UNPKG

@webdevarif/reactflow

Version:

Reusable ReactFlow components for social media bot automation

1,229 lines (1,219 loc) 50.6 kB
// src/facebook/FacebookWorkflow.tsx import React6, { useCallback, useState, useRef, useMemo } from "react"; import ReactFlow, { addEdge, Background, Controls, MiniMap, useNodesState, useEdgesState, ReactFlowProvider } from "reactflow"; import "reactflow/dist/style.css"; import { Plus, Settings, Trash2, Play, Save, Download, Upload } from "lucide-react"; // src/facebook/components/MessageNodes.tsx import { Handle, Position } from "reactflow"; import { jsx, jsxs } from "react/jsx-runtime"; var BaseNode = ({ data, selected }) => { const getIcon = (type) => { switch (type) { case "text": return "\u{1F4AC}"; case "image": return "\u{1F5BC}\uFE0F"; case "audio": return "\u{1F3B5}"; case "video": return "\u{1F3A5}"; case "file": return "\u{1F4C4}"; case "fb_media": return "\u{1F4F1}"; case "carousel": return "\u{1F3A0}"; case "ecommerce": return "\u{1F6D2}"; case "ai_reply": return "\u{1F916}"; default: return "\u2699\uFE0F"; } }; const getColor = (type) => { switch (type) { case "text": return "bg-blue-50 border-blue-200 text-blue-800"; case "image": return "bg-green-50 border-green-200 text-green-800"; case "audio": return "bg-purple-50 border-purple-200 text-purple-800"; case "video": return "bg-red-50 border-red-200 text-red-800"; case "file": return "bg-gray-50 border-gray-200 text-gray-800"; case "fb_media": return "bg-indigo-50 border-indigo-200 text-indigo-800"; case "carousel": return "bg-yellow-50 border-yellow-200 text-yellow-800"; case "ecommerce": return "bg-pink-50 border-pink-200 text-pink-800"; case "ai_reply": return "bg-gradient-to-r from-purple-50 to-pink-50 border-purple-200 text-purple-800"; default: return "bg-gray-50 border-gray-200 text-gray-800"; } }; return /* @__PURE__ */ jsxs("div", { className: `px-4 py-3 shadow-sm rounded-lg border-2 min-w-[200px] max-w-[250px] ${getColor(data.type)} ${selected ? "ring-2 ring-blue-500 shadow-lg" : "hover:shadow-md"} transition-all duration-200`, children: [ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [ /* @__PURE__ */ jsx("span", { className: "text-xl", children: getIcon(data.type) }), /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [ /* @__PURE__ */ jsx("div", { className: "font-semibold text-sm truncate", children: data.label }), data.content && /* @__PURE__ */ jsx("div", { className: "text-xs text-gray-600 mt-1 line-clamp-2", children: data.content }) ] }) ] }), data.mediaUrl && /* @__PURE__ */ jsxs("div", { className: "mt-2 text-xs text-gray-500 flex items-center gap-1", children: [ /* @__PURE__ */ jsx("span", { children: "\u{1F4CE}" }), /* @__PURE__ */ jsx("span", { children: data.mediaType || "media" }) ] }), /* @__PURE__ */ jsx( Handle, { type: "target", position: Position.Top, className: "w-3 h-3 bg-white border-2 border-gray-400 hover:border-blue-500", style: { top: -6 } } ), /* @__PURE__ */ jsx( Handle, { type: "source", position: Position.Bottom, className: "w-3 h-3 bg-white border-2 border-gray-400 hover:border-blue-500", style: { bottom: -6 } } ) ] }); }; var TextMessageNode = (props) => /* @__PURE__ */ jsx(BaseNode, { ...props }); var ImageMessageNode = (props) => /* @__PURE__ */ jsx(BaseNode, { ...props }); var AudioMessageNode = (props) => /* @__PURE__ */ jsx(BaseNode, { ...props }); var VideoMessageNode = (props) => /* @__PURE__ */ jsx(BaseNode, { ...props }); var FileMessageNode = (props) => /* @__PURE__ */ jsx(BaseNode, { ...props }); var FacebookMediaNode = (props) => /* @__PURE__ */ jsx(BaseNode, { ...props }); var CarouselNode = (props) => /* @__PURE__ */ jsx(BaseNode, { ...props }); var EcommerceNode = (props) => /* @__PURE__ */ jsx(BaseNode, { ...props }); var AIReplyNode = (props) => /* @__PURE__ */ jsx(BaseNode, { ...props }); var ConditionNode = ({ data, selected }) => /* @__PURE__ */ jsxs("div", { className: `px-4 py-3 shadow-sm rounded-lg border-2 min-w-[200px] max-w-[250px] bg-orange-50 border-orange-200 text-orange-800 ${selected ? "ring-2 ring-orange-500 shadow-lg" : "hover:shadow-md"} transition-all duration-200`, children: [ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [ /* @__PURE__ */ jsx("span", { className: "text-xl", children: "\u{1F500}" }), /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [ /* @__PURE__ */ jsx("div", { className: "font-semibold text-sm truncate", children: data.label }), data.metadata?.condition && /* @__PURE__ */ jsx("div", { className: "text-xs text-gray-600 mt-1", children: data.metadata.condition }) ] }) ] }), /* @__PURE__ */ jsx( Handle, { type: "target", position: Position.Top, className: "w-3 h-3 bg-white border-2 border-gray-400 hover:border-orange-500", style: { top: -6 } } ), /* @__PURE__ */ jsx( Handle, { type: "source", position: Position.Bottom, className: "w-3 h-3 bg-white border-2 border-gray-400 hover:border-orange-500", style: { bottom: -6 } } ) ] }); var DelayNode = ({ data, selected }) => /* @__PURE__ */ jsxs("div", { className: `px-4 py-3 shadow-sm rounded-lg border-2 min-w-[200px] max-w-[250px] bg-cyan-50 border-cyan-200 text-cyan-800 ${selected ? "ring-2 ring-cyan-500 shadow-lg" : "hover:shadow-md"} transition-all duration-200`, children: [ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [ /* @__PURE__ */ jsx("span", { className: "text-xl", children: "\u23F1\uFE0F" }), /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [ /* @__PURE__ */ jsx("div", { className: "font-semibold text-sm truncate", children: data.label }), data.metadata?.delay && /* @__PURE__ */ jsxs("div", { className: "text-xs text-gray-600 mt-1", children: [ (data.metadata.delay / 1e3).toFixed(1), "s" ] }) ] }) ] }), /* @__PURE__ */ jsx( Handle, { type: "target", position: Position.Top, className: "w-3 h-3 bg-white border-2 border-gray-400 hover:border-cyan-500", style: { top: -6 } } ), /* @__PURE__ */ jsx( Handle, { type: "source", position: Position.Bottom, className: "w-3 h-3 bg-white border-2 border-gray-400 hover:border-cyan-500", style: { bottom: -6 } } ) ] }); var TriggerNode = ({ data, selected }) => { const getIcon = (type) => { switch (type) { case "receive_message": return "\u{1F4AC}"; case "receive_comment": return "\u{1F4AC}"; case "receive_post_reaction": return "\u{1F44D}"; case "receive_page_like": return "\u2764\uFE0F"; default: return "\u26A1"; } }; return /* @__PURE__ */ jsxs("div", { className: `px-4 py-3 shadow-sm rounded-lg border-2 min-w-[200px] max-w-[250px] bg-green-50 border-green-200 text-green-800 ${selected ? "ring-2 ring-green-500 shadow-lg" : "hover:shadow-md"} transition-all duration-200`, children: [ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [ /* @__PURE__ */ jsx("span", { className: "text-xl", children: getIcon(data.type) }), /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [ /* @__PURE__ */ jsx("div", { className: "font-semibold text-sm truncate", children: data.label }), data.metadata?.keywords && /* @__PURE__ */ jsxs("div", { className: "text-xs text-green-600 mt-1 font-medium", children: [ "Keywords: ", data.metadata.keywords ] }) ] }) ] }), /* @__PURE__ */ jsx("div", { className: "mt-2 text-xs text-green-600 font-medium", children: "TRIGGER" }), /* @__PURE__ */ jsx( Handle, { type: "source", position: Position.Bottom, className: "w-3 h-3 bg-white border-2 border-gray-400 hover:border-green-500", style: { bottom: -6 } } ) ] }); }; // src/facebook/config.ts var facebookNodeTypes = { // Trigger nodes receive_message: TriggerNode, receive_comment: TriggerNode, receive_post_reaction: TriggerNode, receive_page_like: TriggerNode, // Action nodes text: TextMessageNode, image: ImageMessageNode, audio: AudioMessageNode, video: VideoMessageNode, file: FileMessageNode, fb_media: FacebookMediaNode, carousel: CarouselNode, ecommerce: EcommerceNode, ai_reply: AIReplyNode, condition: ConditionNode, delay: DelayNode }; var facebookEdgeTypes = { default: "smoothstep", conditional: "smoothstep" }; var facebookSupportedMessageTypes = [ "text", "image", "audio", "video", "file", "fb_media", "carousel", "ecommerce", "ai_reply" ]; var facebookNodeTemplates = { // Trigger nodes receive_message: { type: "receive_message", data: { label: "Receive Message", type: "trigger", metadata: { triggerType: "message", event: "message_received", keywords: "{{ body }}", matchType: "contains", exactMatch: false } }, position: { x: 0, y: 0 } }, receive_comment: { type: "receive_comment", data: { label: "Receive Comment", type: "trigger", metadata: { triggerType: "comment", event: "comment_received", keywords: "{{ body }}", matchType: "contains", exactMatch: false } }, position: { x: 0, y: 0 } }, receive_post_reaction: { type: "receive_post_reaction", data: { label: "Receive Post Reaction", type: "trigger", metadata: { triggerType: "reaction", event: "post_reaction" } }, position: { x: 0, y: 0 } }, receive_page_like: { type: "receive_page_like", data: { label: "Receive Page Like", type: "trigger", metadata: { triggerType: "page_like", event: "page_liked" } }, position: { x: 0, y: 0 } }, // Action nodes text: { type: "text", data: { label: "Text Message", type: "text", content: "Hello! How can I help you today?" }, position: { x: 0, y: 0 } }, image: { type: "image", data: { label: "Image Message", type: "image", content: "Check out this image!", mediaType: "image" }, position: { x: 0, y: 0 } }, audio: { type: "audio", data: { label: "Audio Message", type: "audio", content: "Listen to this audio", mediaType: "audio" }, position: { x: 0, y: 0 } }, video: { type: "video", data: { label: "Video Message", type: "video", content: "Watch this video", mediaType: "video" }, position: { x: 0, y: 0 } }, file: { type: "file", data: { label: "File Message", type: "file", content: "Here is your file", mediaType: "file" }, position: { x: 0, y: 0 } }, fb_media: { type: "fb_media", data: { label: "Facebook Media", type: "fb_media", content: "Facebook media content", mediaType: "image" }, position: { x: 0, y: 0 } }, carousel: { type: "carousel", data: { label: "Carousel", type: "carousel", content: "Browse our products", metadata: { items: [] } }, position: { x: 0, y: 0 } }, ecommerce: { type: "ecommerce", data: { label: "E-commerce", type: "ecommerce", content: "Shop now", metadata: { products: [] } }, position: { x: 0, y: 0 } }, ai_reply: { type: "ai_reply", data: { label: "AI Reply", type: "ai_reply", content: "AI-powered response", metadata: { prompt: "", model: "gpt-3.5-turbo" } }, position: { x: 0, y: 0 } }, condition: { type: "condition", data: { label: "Condition", type: "condition", metadata: { condition: "user_input", operator: "contains", value: "help" } }, position: { x: 0, y: 0 } }, delay: { type: "delay", data: { label: "Delay", type: "delay", metadata: { delay: 300 } }, position: { x: 0, y: 0 } } }; // src/components/ui/button.tsx import * as React from "react"; import { Slot } from "@radix-ui/react-slot"; import { cva } from "class-variance-authority"; // src/lib/utils.ts import { clsx } from "clsx"; import { twMerge } from "tailwind-merge"; function cn(...inputs) { return twMerge(clsx(inputs)); } // src/components/ui/button.tsx import { jsx as jsx2 } from "react/jsx-runtime"; var buttonVariants = cva( "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", { variants: { variant: { default: "bg-primary text-primary-foreground hover:bg-primary/90", destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90", outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground", secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80", ghost: "hover:bg-accent hover:text-accent-foreground", link: "text-primary underline-offset-4 hover:underline" }, size: { default: "h-10 px-4 py-2", sm: "h-9 rounded-md px-3", lg: "h-11 rounded-md px-8", icon: "h-10 w-10" } }, defaultVariants: { variant: "default", size: "default" } } ); var Button = React.forwardRef( ({ className, variant, size, asChild = false, ...props }, ref) => { const Comp = asChild ? Slot : "button"; return /* @__PURE__ */ jsx2( Comp, { className: cn(buttonVariants({ variant, size, className })), ref, ...props } ); } ); Button.displayName = "Button"; // src/components/ui/input.tsx import * as React2 from "react"; import { jsx as jsx3 } from "react/jsx-runtime"; var Input = React2.forwardRef( ({ className, type, ...props }, ref) => { return /* @__PURE__ */ jsx3( "input", { type, className: cn( "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", className ), ref, ...props } ); } ); Input.displayName = "Input"; // src/components/ui/select.tsx import * as React3 from "react"; import * as SelectPrimitive from "@radix-ui/react-select"; import { Check, ChevronDown, ChevronUp } from "lucide-react"; import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime"; var Select = SelectPrimitive.Root; var SelectValue = SelectPrimitive.Value; var SelectTrigger = React3.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxs2( SelectPrimitive.Trigger, { ref, className: cn( "flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1", className ), ...props, children: [ children, /* @__PURE__ */ jsx4(SelectPrimitive.Icon, { asChild: true, children: /* @__PURE__ */ jsx4(ChevronDown, { className: "h-4 w-4 opacity-50" }) }) ] } )); SelectTrigger.displayName = SelectPrimitive.Trigger.displayName; var SelectScrollUpButton = React3.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx4( SelectPrimitive.ScrollUpButton, { ref, className: cn( "flex cursor-default items-center justify-center py-1", className ), ...props, children: /* @__PURE__ */ jsx4(ChevronUp, { className: "h-4 w-4" }) } )); SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName; var SelectScrollDownButton = React3.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx4( SelectPrimitive.ScrollDownButton, { ref, className: cn( "flex cursor-default items-center justify-center py-1", className ), ...props, children: /* @__PURE__ */ jsx4(ChevronDown, { className: "h-4 w-4" }) } )); SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName; var SelectContent = React3.forwardRef(({ className, children, position = "popper", ...props }, ref) => /* @__PURE__ */ jsx4(SelectPrimitive.Portal, { children: /* @__PURE__ */ jsxs2( SelectPrimitive.Content, { ref, className: cn( "relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", position === "popper" && "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1", className ), position, ...props, children: [ /* @__PURE__ */ jsx4(SelectScrollUpButton, {}), /* @__PURE__ */ jsx4( SelectPrimitive.Viewport, { className: cn( "p-1", position === "popper" && "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]" ), children } ), /* @__PURE__ */ jsx4(SelectScrollDownButton, {}) ] } ) })); SelectContent.displayName = SelectPrimitive.Content.displayName; var SelectLabel = React3.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx4( SelectPrimitive.Label, { ref, className: cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className), ...props } )); SelectLabel.displayName = SelectPrimitive.Label.displayName; var SelectItem = React3.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxs2( SelectPrimitive.Item, { ref, className: cn( "relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", className ), ...props, children: [ /* @__PURE__ */ jsx4("span", { className: "absolute left-2 flex h-3.5 w-3.5 items-center justify-center", children: /* @__PURE__ */ jsx4(SelectPrimitive.ItemIndicator, { children: /* @__PURE__ */ jsx4(Check, { className: "h-4 w-4" }) }) }), /* @__PURE__ */ jsx4(SelectPrimitive.ItemText, { children }) ] } )); SelectItem.displayName = SelectPrimitive.Item.displayName; var SelectSeparator = React3.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx4( SelectPrimitive.Separator, { ref, className: cn("-mx-1 my-1 h-px bg-muted", className), ...props } )); SelectSeparator.displayName = SelectPrimitive.Separator.displayName; // src/components/ui/switch.tsx import * as React4 from "react"; import * as SwitchPrimitives from "@radix-ui/react-switch"; import { jsx as jsx5 } from "react/jsx-runtime"; var Switch = React4.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx5( SwitchPrimitives.Root, { className: cn( "peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input", className ), ...props, ref, children: /* @__PURE__ */ jsx5( SwitchPrimitives.Thumb, { className: cn( "pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0" ) } ) } )); Switch.displayName = SwitchPrimitives.Root.displayName; // src/components/ui/textarea.tsx import * as React5 from "react"; import { jsx as jsx6 } from "react/jsx-runtime"; var Textarea = React5.forwardRef( ({ className, ...props }, ref) => { return /* @__PURE__ */ jsx6( "textarea", { className: cn( "flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", className ), ref, ...props } ); } ); Textarea.displayName = "Textarea"; // src/facebook/FacebookWorkflow.tsx import { Fragment, jsx as jsx7, jsxs as jsxs3 } from "react/jsx-runtime"; var FacebookWorkflow = ({ initialNodes = [], initialEdges = [], initialTitle = "Facebook Bot Workflow", onSave, onNodeClick, onEdgeClick, onTitleChange, className = "", height = "600px" }) => { const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes); const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); const [selectedNode, setSelectedNode] = useState(null); const [showTriggerPopup, setShowTriggerPopup] = useState(false); const [showSidebar, setShowSidebar] = useState(true); const [sidebarTab, setSidebarTab] = useState("nodes"); const [isDragOver, setIsDragOver] = useState(false); const [isClient, setIsClient] = useState(false); const [forceUpdate, setForceUpdate] = useState(0); const [workflowTitle, setWorkflowTitle] = useState(initialTitle); const [isEditingTitle, setIsEditingTitle] = useState(false); const reactFlowInstance = useRef(null); React6.useEffect(() => { setIsClient(true); }, []); const memoizedNodeTypes = useMemo(() => facebookNodeTypes, []); const memoizedEdgeTypes = useMemo(() => facebookEdgeTypes, []); const forceRerender = useCallback(() => { setForceUpdate((prev) => prev + 1); }, []); const handleTitleChange = useCallback((newTitle) => { setWorkflowTitle(newTitle); onTitleChange?.(newTitle); }, [onTitleChange]); const handleTitleSubmit = useCallback(() => { setIsEditingTitle(false); onTitleChange?.(workflowTitle); }, [workflowTitle, onTitleChange]); const handleTitleKeyDown = useCallback((e) => { if (e.key === "Enter") { handleTitleSubmit(); } else if (e.key === "Escape") { setWorkflowTitle(initialTitle); setIsEditingTitle(false); } }, [handleTitleSubmit, initialTitle]); const onConnect = useCallback( (params) => setEdges((eds) => addEdge(params, eds)), [setEdges] ); const handleNodeClick = useCallback((event, node) => { setSelectedNode(node); setSidebarTab("properties"); onNodeClick?.(node); }, [onNodeClick]); const handleEdgeClick = useCallback((event, edge) => { onEdgeClick?.(edge); }, [onEdgeClick]); const addNode = useCallback((nodeType, position) => { const template = facebookNodeTemplates[nodeType]; if (template) { const newNode = { ...template, id: `${nodeType}_${Date.now()}`, position: position || { x: Math.random() * 400 + 200, y: Math.random() * 400 + 200 } }; setNodes((nds) => [...nds, newNode]); } setShowTriggerPopup(false); }, [setNodes]); const onInit = useCallback((instance) => { reactFlowInstance.current = instance; }, []); const onDragOver = useCallback((event) => { event.preventDefault(); event.dataTransfer.dropEffect = "move"; setIsDragOver(true); }, []); const onDrop = useCallback((event) => { event.preventDefault(); setIsDragOver(false); const nodeType = event.dataTransfer.getData("application/reactflow"); if (!nodeType || !reactFlowInstance.current) { return; } const position = reactFlowInstance.current.screenToFlowPosition({ x: event.clientX, y: event.clientY }); addNode(nodeType, position); }, [addNode]); const onDragLeave = useCallback(() => { setIsDragOver(false); }, []); const onDragStart = useCallback((event, nodeType) => { event.dataTransfer.setData("application/reactflow", nodeType); event.dataTransfer.effectAllowed = "move"; }, []); const deleteNode = useCallback((nodeId) => { setNodes((nds) => nds.filter((node) => node.id !== nodeId)); setEdges((eds) => eds.filter((edge) => edge.source !== nodeId && edge.target !== nodeId)); setSelectedNode(null); }, [setNodes, setEdges]); const saveWorkflow = useCallback(() => { const config = { name: workflowTitle, description: "Automated Facebook Messenger bot workflow", platform: "facebook", nodes, edges }; onSave?.(config); }, [nodes, edges, workflowTitle, onSave]); const nodeGroups = { "Triggers": [ { type: "receive_message", label: "Receive Message", icon: "\u{1F4E8}", description: "Triggered when user sends a message" }, { type: "receive_comment", label: "Receive Comment", icon: "\u{1F4AC}", description: "Triggered when user comments on post" }, { type: "receive_post_reaction", label: "Receive Post Reaction", icon: "\u{1F44D}", description: "Triggered when user reacts to post" }, { type: "receive_page_like", label: "Receive Page Like", icon: "\u2764\uFE0F", description: "Triggered when user likes the page" } ], "Messages": [ { type: "text", label: "Text Message", icon: "\u{1F4AC}", description: "Send a text message" }, { type: "image", label: "Image Message", icon: "\u{1F5BC}\uFE0F", description: "Send an image" }, { type: "audio", label: "Audio Message", icon: "\u{1F3B5}", description: "Send audio" }, { type: "video", label: "Video Message", icon: "\u{1F3A5}", description: "Send video" } ], "Media": [ { type: "file", label: "File Message", icon: "\u{1F4C4}", description: "Send a file" }, { type: "fb_media", label: "Facebook Media", icon: "\u{1F4F1}", description: "Facebook media content" }, { type: "carousel", label: "Carousel", icon: "\u{1F3A0}", description: "Carousel of items" } ], "Commerce": [ { type: "ecommerce", label: "E-commerce", icon: "\u{1F6D2}", description: "E-commerce products" } ], "AI & Logic": [ { type: "ai_reply", label: "AI Reply", icon: "\u{1F916}", description: "AI-powered response" }, { type: "condition", label: "Condition", icon: "\u{1F500}", description: "Conditional logic" }, { type: "delay", label: "Delay", icon: "\u23F1\uFE0F", description: "Add delay" } ] }; if (!isClient) { return /* @__PURE__ */ jsx7("div", { className: `w-full h-full bg-gray-50 flex items-center justify-center ${className}`, style: { height }, children: /* @__PURE__ */ jsxs3("div", { className: "text-center", children: [ /* @__PURE__ */ jsx7("div", { className: "animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600 mx-auto mb-4" }), /* @__PURE__ */ jsx7("p", { className: "text-gray-600", children: "Loading workflow builder..." }) ] }) }); } return /* @__PURE__ */ jsxs3("div", { className: `w-full h-full bg-gray-50 flex flex-col ${className}`, style: { height }, children: [ /* @__PURE__ */ jsxs3("div", { className: "h-14 bg-white border-b border-gray-200 flex items-center justify-between px-4", children: [ /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-4", children: [ isEditingTitle ? /* @__PURE__ */ jsx7( Input, { value: workflowTitle, onChange: (e) => setWorkflowTitle(e.target.value), onBlur: handleTitleSubmit, onKeyDown: handleTitleKeyDown, className: "text-lg font-semibold text-gray-900 bg-transparent border-none p-0 focus:ring-0 focus:border-none", autoFocus: true } ) : /* @__PURE__ */ jsx7( "h1", { className: "text-lg font-semibold text-gray-900 cursor-pointer hover:bg-gray-100 px-2 py-1 rounded transition-colors", onClick: () => setIsEditingTitle(true), title: "Click to edit workflow title", children: workflowTitle } ), /* @__PURE__ */ jsx7("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ jsx7( "button", { onClick: () => setShowSidebar(!showSidebar), className: "p-2 hover:bg-gray-100 rounded-md transition-colors", children: /* @__PURE__ */ jsx7(Settings, { className: "w-4 h-4" }) } ) }) ] }), /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2", children: [ /* @__PURE__ */ jsxs3(Button, { variant: "outline", size: "sm", children: [ /* @__PURE__ */ jsx7(Upload, { className: "w-4 h-4 mr-2" }), "Import" ] }), /* @__PURE__ */ jsxs3(Button, { variant: "outline", size: "sm", children: [ /* @__PURE__ */ jsx7(Download, { className: "w-4 h-4 mr-2" }), "Export" ] }), /* @__PURE__ */ jsxs3(Button, { variant: "default", size: "sm", children: [ /* @__PURE__ */ jsx7(Play, { className: "w-4 h-4 mr-2" }), "Test" ] }), /* @__PURE__ */ jsxs3( Button, { onClick: saveWorkflow, variant: "default", size: "sm", className: "bg-green-600 hover:bg-green-700", children: [ /* @__PURE__ */ jsx7(Save, { className: "w-4 h-4 mr-2" }), "Save" ] } ) ] }) ] }), /* @__PURE__ */ jsxs3("div", { className: "flex h-[calc(100%-3.5rem)] flex-1", children: [ showSidebar && /* @__PURE__ */ jsxs3("div", { className: "w-80 bg-white border-r border-gray-200 flex flex-col h-full", children: [ /* @__PURE__ */ jsxs3("div", { className: "flex border-b border-gray-200", children: [ /* @__PURE__ */ jsx7( "button", { onClick: () => setSidebarTab("nodes"), className: `flex-1 px-4 py-3 text-sm font-medium transition-colors ${sidebarTab === "nodes" ? "text-blue-600 border-b-2 border-blue-600 bg-blue-50" : "text-gray-600 hover:text-gray-900"}`, children: "Nodes" } ), /* @__PURE__ */ jsx7( "button", { onClick: () => setSidebarTab("properties"), className: `flex-1 px-4 py-3 text-sm font-medium transition-colors ${sidebarTab === "properties" ? "text-blue-600 border-b-2 border-blue-600 bg-blue-50" : "text-gray-600 hover:text-gray-900"}`, children: "Properties" } ) ] }), /* @__PURE__ */ jsx7("div", { className: "flex-1 overflow-y-auto", children: sidebarTab === "nodes" ? /* @__PURE__ */ jsx7("div", { className: "p-4", children: nodes.length === 0 ? ( // Show only triggers when canvas is empty /* @__PURE__ */ jsxs3("div", { className: "mb-6", children: [ /* @__PURE__ */ jsx7("h3", { className: "text-sm font-semibold text-gray-900 mb-3 uppercase tracking-wide", children: "Triggers" }), /* @__PURE__ */ jsx7("div", { className: "space-y-2", children: nodeGroups["Triggers"].map((node) => /* @__PURE__ */ jsxs3( "div", { draggable: true, onDragStart: (event) => onDragStart(event, node.type), onClick: () => addNode(node.type), className: "w-full flex items-center gap-3 p-3 text-left hover:bg-gray-50 rounded-lg border border-gray-200 transition-colors group cursor-grab active:cursor-grabbing", children: [ /* @__PURE__ */ jsx7("span", { className: "text-2xl", children: node.icon }), /* @__PURE__ */ jsxs3("div", { className: "flex-1 min-w-0", children: [ /* @__PURE__ */ jsx7("div", { className: "text-sm font-medium text-gray-900 group-hover:text-blue-600", children: node.label }), /* @__PURE__ */ jsx7("div", { className: "text-xs text-gray-500 mt-0.5", children: node.description }) ] }) ] }, node.type )) }) ] }) ) : ( // Show action nodes when canvas has nodes (hide triggers) Object.entries(nodeGroups).filter(([groupName]) => groupName !== "Triggers").map(([groupName, groupNodes]) => /* @__PURE__ */ jsxs3("div", { className: "mb-6", children: [ /* @__PURE__ */ jsx7("h3", { className: "text-sm font-semibold text-gray-900 mb-3 uppercase tracking-wide", children: groupName }), /* @__PURE__ */ jsx7("div", { className: "space-y-2", children: groupNodes.map((node) => /* @__PURE__ */ jsxs3( "div", { draggable: true, onDragStart: (event) => onDragStart(event, node.type), onClick: () => addNode(node.type), className: "w-full flex items-center gap-3 p-3 text-left hover:bg-gray-50 rounded-lg border border-gray-200 transition-colors group cursor-grab active:cursor-grabbing", children: [ /* @__PURE__ */ jsx7("span", { className: "text-2xl", children: node.icon }), /* @__PURE__ */ jsxs3("div", { className: "flex-1 min-w-0", children: [ /* @__PURE__ */ jsx7("div", { className: "text-sm font-medium text-gray-900 group-hover:text-blue-600", children: node.label }), /* @__PURE__ */ jsx7("div", { className: "text-xs text-gray-500 mt-0.5", children: node.description }) ] }) ] }, node.type )) }) ] }, groupName)) ) }) : /* @__PURE__ */ jsx7("div", { className: "p-4", children: selectedNode ? /* @__PURE__ */ jsxs3("div", { className: "space-y-4", children: [ /* @__PURE__ */ jsxs3("div", { children: [ /* @__PURE__ */ jsx7("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: "Node Type" }), /* @__PURE__ */ jsx7("div", { className: "text-sm text-gray-900 bg-gray-50 px-3 py-2 rounded-md", children: selectedNode.type }) ] }), /* @__PURE__ */ jsxs3("div", { children: [ /* @__PURE__ */ jsx7("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: "Label" }), /* @__PURE__ */ jsx7( Input, { type: "text", value: selectedNode.data.label, onChange: (e) => { setNodes((nds) => nds.map( (n) => n.id === selectedNode.id ? { ...n, data: { ...n.data, label: e.target.value } } : n )); setSelectedNode((prev) => prev ? { ...prev, data: { ...prev.data, label: e.target.value } } : null); forceRerender(); } } ) ] }), selectedNode.data.type !== "trigger" && selectedNode.data.content !== void 0 && /* @__PURE__ */ jsxs3("div", { children: [ /* @__PURE__ */ jsx7("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: "Content" }), /* @__PURE__ */ jsx7( Textarea, { value: selectedNode.data.content || "", onChange: (e) => { setNodes((nds) => nds.map( (n) => n.id === selectedNode.id ? { ...n, data: { ...n.data, content: e.target.value } } : n )); setSelectedNode((prev) => prev ? { ...prev, data: { ...prev.data, content: e.target.value } } : null); forceRerender(); }, rows: 3 } ) ] }), selectedNode.data.type === "trigger" && /* @__PURE__ */ jsxs3(Fragment, { children: [ /* @__PURE__ */ jsxs3("div", { children: [ /* @__PURE__ */ jsx7("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: "Keywords" }), /* @__PURE__ */ jsx7( Textarea, { placeholder: "hello, help, {{ body }}", value: selectedNode.data.metadata?.keywords || "", onChange: (e) => { setNodes((nds) => nds.map( (n) => n.id === selectedNode.id ? { ...n, data: { ...n.data, metadata: { ...n.data.metadata, keywords: e.target.value } } } : n )); setSelectedNode((prev) => prev ? { ...prev, data: { ...prev.data, metadata: { ...prev.data.metadata, keywords: e.target.value } } } : null); forceRerender(); }, rows: 3 } ), /* @__PURE__ */ jsxs3("p", { className: "text-xs text-gray-500 mt-1", children: [ "Comma separated keywords or ", `{{ body }}`, " for full content" ] }) ] }), /* @__PURE__ */ jsxs3("div", { children: [ /* @__PURE__ */ jsx7("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: "Match Type" }), /* @__PURE__ */ jsxs3( Select, { value: selectedNode.data.metadata?.matchType || "contains", onValueChange: (value) => { setNodes((nds) => nds.map( (n) => n.id === selectedNode.id ? { ...n, data: { ...n.data, metadata: { ...n.data.metadata, matchType: value } } } : n )); setSelectedNode((prev) => prev ? { ...prev, data: { ...prev.data, metadata: { ...prev.data.metadata, matchType: value } } } : null); forceRerender(); }, children: [ /* @__PURE__ */ jsx7(SelectTrigger, { children: /* @__PURE__ */ jsx7(SelectValue, { placeholder: "Select match type" }) }), /* @__PURE__ */ jsxs3(SelectContent, { children: [ /* @__PURE__ */ jsx7(SelectItem, { value: "contains", children: "Contains" }), /* @__PURE__ */ jsx7(SelectItem, { value: "exact", children: "Exact Match" }), /* @__PURE__ */ jsx7(SelectItem, { value: "starts_with", children: "Starts With" }), /* @__PURE__ */ jsx7(SelectItem, { value: "ends_with", children: "Ends With" }) ] }) ] } ) ] }), /* @__PURE__ */ jsxs3("div", { className: "flex items-center justify-between", children: [ /* @__PURE__ */ jsxs3("div", { children: [ /* @__PURE__ */ jsx7("label", { htmlFor: "exactMatch", className: "text-sm font-medium text-gray-700", children: "Exact Match (case sensitive)" }), /* @__PURE__ */ jsx7("p", { className: "text-xs text-gray-500 mt-1", children: "Enable case-sensitive matching" }) ] }), /* @__PURE__ */ jsx7("div", { className: "flex items-center", children: /* @__PURE__ */ jsx7( Switch, { checked: selectedNode.data.metadata?.exactMatch || false, onCheckedChange: (checked) => { console.log("Switch toggled:", { checked, nodeId: selectedNode.id }); setNodes((nds) => nds.map( (n) => n.id === selectedNode.id ? { ...n, data: { ...n.data, metadata: { ...n.data.metadata, exactMatch: checked } } } : n )); setSelectedNode((prev) => prev ? { ...prev, data: { ...prev.data, metadata: { ...prev.data.metadata, exactMatch: checked } } } : null); } } ) }) ] }) ] }), selectedNode.data.type === "delay" && /* @__PURE__ */ jsxs3("div", { children: [ /* @__PURE__ */ jsx7("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: "Delay Duration (milliseconds)" }), /* @__PURE__ */ jsx7( Input, { type: "number", min: "0", step: "100", value: selectedNode.data.metadata?.delay || 300, onChange: (e) => { const delayValue = parseInt(e.target.value) || 300; setNodes((nds) => nds.map( (n) => n.id === selectedNode.id ? { ...n, data: { ...n.data, metadata: { ...n.data.metadata, delay: delayValue } } } : n )); setSelectedNode((prev) => prev ? { ...prev, data: { ...prev.data, metadata: { ...prev.data.metadata, delay: delayValue } } } : null); forceRerender(); }, placeholder: "300" } ), /* @__PURE__ */ jsx7("p", { className: "text-xs text-gray-500 mt-1", children: "Delay in milliseconds (default: 300ms)" }) ] }), /* @__PURE__ */ jsxs3( Button, { onClick: () => deleteNode(selectedNode.id), variant: "destructive", className: "w-full", children: [ /* @__PURE__ */ jsx7(Trash2, { className: "w-4 h-4 mr-2" }), "Delete Node" ] } ) ] }) : /* @__PURE__ */ jsxs3("div", { className: "text-center py-8", children: [ /* @__PURE__ */ jsx7("div", { className: "text-gray-400 mb-2", children: /* @__PURE__ */ jsx7(Settings, { className: "w-8 h-8 mx-auto" }) }), /* @__PURE__ */ jsx7("p", { className: "text-sm text-gray-500", children: "Select a node to edit its properties" }) ] }) }) }) ] }), /* @__PURE__ */ jsxs3("div", { className: `flex-1 relative ${isDragOver ? "bg-blue-50 border-2 border-dashed border-blue-300" : ""}`, children: [ /* @__PURE__ */ jsxs3( ReactFlow, { nodes, edges, onNodesChange, onEdgesChange, onConnect, onNodeClick: handleNodeClick, onEdgeClick: handleEdgeClick, onInit, onDrop, onDragOver, onDragLeave, nodeTypes: memoizedNodeTypes, fitView: true, attributionPosition: "bottom-left", className: "bg-gray-50", children: [ /* @__PURE__ */ jsx7(Background, { color: "#e5e7eb", gap: 20 }), /* @__PURE__ */ jsx7(Controls, { className: "bg-white border border-gray-200 rounded-lg shadow-sm" }), /* @__PURE__ */ jsx7( MiniMap, { className: "bg-white border border-gray-200 rounded-lg shadow-sm", nodeColor: "#3b82f6", maskColor: "rgba(0, 0, 0, 0.1)" } ) ] } ), nodes.length === 0 && !isDragOver && /* @__PURE__ */ jsx7("div", { className: "absolute inset-0 flex items-center justify-center pointer-events-none", children: /* @__PURE__ */ jsxs3("div", { className: "text-center", children: [ /* @__PURE__ */ jsx7( "button", { onClick: () => setShowTriggerPopup(true), className: "mx-auto w-16 h-16 bg-blue-600 hover:bg-blue-700 text-white rounded-full shadow-lg hover:shadow-xl transition-all duration-200 flex items-center justify-center pointer-events-auto group", children: /* @__PURE__ */ jsx7(Plus, { className: "w-8 h-8 group-hover:scale-110 transition-transform" }) } ), /* @__PURE__ */ jsx7("p", { className: "mt-4 text-sm text-gray-500 font-medium", children: "Click to add your first node" }) ] }) }), isDragOver && /* @__PURE__ */ jsx7("div", { className: "absolute inset-0 flex items-center justify-center pointer-events-none", children: /* @__PURE__ */ jsxs3("div", { className: "text-center", children: [ /* @__PURE__ */ jsx7("div", { className: "w-20 h-20 bg-blue-100 border-4 border-dashed border-blue-400 rounded-full flex items-center justify-center", children: /* @__PURE__ */ jsx7(Plus, { className: "w-10 h-10 text-blue-600" }) }), /* @__PURE__ */ jsx7("p", { className: "mt-4 text-lg text-blue-600 font-semibold", children: "Drop node here" }) ] }) }) ] }) ] }), showTriggerPopup && /* @__PURE__ */ jsx7("div", { className: "fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50", children: /* @__PURE__ */ jsxs3("div", { className: "bg-white rounded-lg shadow-xl max-w-2xl w-full mx-4 max-h-[80vh] overflow-hidden", children: [ /* @__PURE__ */ jsx7("div", { className: "p-6 border-b border-gray-200", children: /* @__PURE__ */ jsxs3("div", { className: "flex items-center justify-between", children: [ /* @__PURE__ */ jsx7("h2", { className: "text-lg font-semibold text-gray-900", children: "Choose a Trigger" }), /* @__PURE__ */ jsx7( "button", { onClick: () => setShowTriggerPopup(false), className: "text-gray-400 hover:text-gray-600", children: /* @__PURE__ */ jsx7(Plus, { className: "w-6 h-6 rotate-45" }) } ) ] }) }), /* @__PURE__ */ jsx7("div", { className: "p-6 overflow-y-auto max-h-[60vh]", children: /* @__PURE__ */ jsxs3("div", { className: "mb-6", children: [ /* @__PURE__ */ jsx7("h3", { className: "text-sm font-semibold text-gray-900 mb-3 uppercase tracking-wide", children: "Triggers" }), /* @__PURE__ */ jsx7("div", { className: "grid grid-cols-2 gap-3", children: nodeGroups["Triggers"].map((node) => /* @__PURE__ */ jsxs3( "div", { draggable: true, onDragStart: (event) => onDragStart(event, node.type), onClick: () => addNode(node.type), className: "flex items-center gap-3 p-4 text-left hover:bg-gray-50 rounded-lg border border-gray-200 transition-colors group cursor-grab active:cursor-grabbing", children: [ /* @__PURE__ */ jsx7("span", { className: "text-2xl", children: node.icon }), /* @__PURE__ */ jsxs3("div", { className: "flex-1 min-w-0", children: [ /* @__PURE__ */ jsx7("div", { className: "text-sm font-medium text-gray-900 group-hover:text-blue-600", children: node.label }), /* @__PURE__ */ jsx7("div", { className: "text-xs text-gray-500 mt-0.5", children: node.description }) ] }) ] }, node.type )) }) ] }) }) ] }) }) ] }); }; var FacebookWorkflowWithProvider = (props) => /* @__PURE__