From 04ff6ade703ee1ad3257f6b78ef73f7dd1ff9fc8 Mon Sep 17 00:00:00 2001 From: Vamsi_0 Date: Fri, 12 Dec 2025 17:54:52 +0530 Subject: [PATCH] Action Node Selection Done --- apps/http-backend/src/index.ts | 6 +- apps/http-backend/tsconfig.tsbuildinfo | 2 +- .../web/app/components/Actions/ActionNode.tsx | 26 ++ .../app/components/Actions/ActionSidebar.tsx | 79 ++++++ .../app/components/nodes/CreateWorkFlow.tsx | 244 +++++++++++------- apps/web/app/hooks/useActions.tsx | 34 +++ apps/web/app/page.tsx | 8 +- apps/web/app/types/workflow.types.ts | 3 +- package.json | 2 +- 9 files changed, 299 insertions(+), 105 deletions(-) create mode 100644 apps/web/app/components/Actions/ActionNode.tsx create mode 100644 apps/web/app/components/Actions/ActionSidebar.tsx create mode 100644 apps/web/app/hooks/useActions.tsx diff --git a/apps/http-backend/src/index.ts b/apps/http-backend/src/index.ts index 681174a..b0d364a 100644 --- a/apps/http-backend/src/index.ts +++ b/apps/http-backend/src/index.ts @@ -2,14 +2,16 @@ // import axios from "axios"; import { prismaClient } from "@repo/db/client"; import cookieParser from 'cookie-parser' -import { NodeRegistry } from "@repo/nodes/nodeClinet"; + + +import { NodeRegistry } from "@repo/nodes/nodeClient"; import express from "express"; import { userRouter } from "./routes/userRoutes/userRoutes.js"; import cors from "cors" const app = express() -const allowedOrigins = ['http://localhost:3000']; +const allowedOrigins = ['http://localhost:3000' , 'http://localhost:3001' ]; app.use(cors({ origin: allowedOrigins, credentials: true, diff --git a/apps/http-backend/tsconfig.tsbuildinfo b/apps/http-backend/tsconfig.tsbuildinfo index e49b868..f8e5606 100644 --- a/apps/http-backend/tsconfig.tsbuildinfo +++ b/apps/http-backend/tsconfig.tsbuildinfo @@ -1 +1 @@ -{"root":["./src/index.ts","./src/routes/nodes.routes.ts","./src/routes/userroutes/usermiddleware.ts","./src/routes/userroutes/userroutes.ts"],"version":"5.7.3"} \ No newline at end of file +{"root":["./src/index.ts","./src/routes/nodes.routes.ts","./src/routes/userRoutes/userMiddleware.ts","./src/routes/userRoutes/userRoutes.ts"],"version":"5.7.3"} \ No newline at end of file diff --git a/apps/web/app/components/Actions/ActionNode.tsx b/apps/web/app/components/Actions/ActionNode.tsx new file mode 100644 index 0000000..eb41040 --- /dev/null +++ b/apps/web/app/components/Actions/ActionNode.tsx @@ -0,0 +1,26 @@ +import { Handle, Position } from "@xyflow/react"; + +interface ActionNodeProps { + data: { + label: string; + name?: string; + icon?: string; + type?: string; + }; +} + +export const ActionNode = ({ data }: ActionNodeProps) => { + return ( +
+ + + {data.icon || "⚙️"} + {data.name} + {data.type} + + +
+ ); +}; + +export default ActionNode; \ No newline at end of file diff --git a/apps/web/app/components/Actions/ActionSidebar.tsx b/apps/web/app/components/Actions/ActionSidebar.tsx new file mode 100644 index 0000000..8a38629 --- /dev/null +++ b/apps/web/app/components/Actions/ActionSidebar.tsx @@ -0,0 +1,79 @@ +import { + Sheet, + SheetContent, + SheetHeader, + SheetTitle, +} from "@workspace/ui/components/sheet"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@workspace/ui/components/select"; +import { useActions } from "@/app/hooks/useActions"; + +interface SideBarProps { + isOpen: boolean; + onClose: () => void; + onSelectAction: (action: { id: string; name: string; type: string; icon?: string }) => void; +} + +const getAvailableActions = (actions: any): Array => { + if (Array.isArray(actions)) return actions; + if (actions && Array.isArray(actions.Data)) return actions.Data; + return []; +}; + +export const ActionSideBar = ({ isOpen, onClose, onSelectAction }: SideBarProps) => { + const { actions, loading, error } = useActions({ shouldFetch: true }); + const availableActions = getAvailableActions(actions); + + return ( + !open && onClose()}> + + + Select an Action + + + {loading ? ( +

Loading...

+ ) : error ? ( +

Error fetching actions

+ ) : ( + + )} +
+
+ ); +}; + +export default ActionSideBar; diff --git a/apps/web/app/components/nodes/CreateWorkFlow.tsx b/apps/web/app/components/nodes/CreateWorkFlow.tsx index 577b17c..7a8cded 100644 --- a/apps/web/app/components/nodes/CreateWorkFlow.tsx +++ b/apps/web/app/components/nodes/CreateWorkFlow.tsx @@ -4,151 +4,205 @@ import "@xyflow/react/dist/style.css"; import { ReactFlow } from "@xyflow/react"; import PlaceholderNode from "./PlaceHolder"; import { TriggerNode } from "./TriggerNode"; + import { TriggerSideBar } from "./TriggerSidebar"; +import ActionSideBar from "../Actions/ActionSidebar"; +import ActionNode from "../Actions/ActionNode"; + interface NodeType { - id: string; - type: "placeholder" | "trigger"; - position: { x: number; y: number }; - data: { - label: string; - name?: string; - icon?: string; - type?: string; - config?: Record; - }; - } - interface EdgeType { - id: string; - source: string; - target: string; - } - + id: string; + type: "placeholder" | "trigger" | "action"; + position: { x: number; y: number }; + data: { + label: string; + name?: string; + icon?: string; + type?: string; + config?: Record; + }; +} + +interface EdgeType { + id: string; + source: string; + target: string; +} + export const CreateWorkFlow = () => { - const [sidebaropen, setSideBarOpen] = useState(false); + const [sidebarOpen, setSidebarOpen] = useState(false); + const [actionSidebarOpen, setActionSidebarOpen] = useState(false); + const [nodes, setNodes] = useState([ { id: "1", type: "placeholder", - position: { x: 0, y: 0 }, + position: { x: 100, y: 200 }, data: { - label: "Select This one", - icon: "→", + label: "+", }, - }, + }, ]); + const [edges, setEdges] = useState([]); - const nodeType = { + const nodeTypes = { placeholder: PlaceholderNode, trigger: TriggerNode, + action: ActionNode, }; -// const handleSelectTrigger = (trigger: { id: string; name: string; type: string; icon?: string }) => { -// const edgeId = `e-trigger-${trigger.id}-placeholder-${Date.now()}`; -// const placeholderId = `placeholder-${Date.now()}`; - -// setNodes((currentNodes) => { -// const placeholderNode = currentNodes.find((n) => n.type === "placeholder"); -// if (!placeholderNode) return currentNodes; - -// const triggerNode: NodeType = { -// id: `trigger-${trigger.id}`, -// type: "trigger", -// position: placeholderNode.position, -// data: { -// label: trigger.name, -// name: trigger.name, -// icon: trigger.icon || "⚡", -// type: trigger.type, -// }, -// }; - -// const newPlaceholder: NodeType = { -// id: placeholderId, -// type: "placeholder", -// position: { -// x: placeholderNode.position.x + 200, -// y: placeholderNode.position.y, -// }, -// data: { -// label: "+", -// }, -// }; - -// return [triggerNode, newPlaceholder]; -// }); - -// // TypeScript expects you to define a type for edges. -// // We'll use a generic structure: id (string), source (string), target (string) -// setEdges([ -// { -// id: edgeId, -// source: `trigger-${trigger.id}`, -// target: placeholderId, -// } -// ]); -// }; - - -const handleSelectTrigger = (trigger: { id: string; name: string; type: string; icon?: string }) => { - const timestamp = Date.now(); // ✅ Call ONCE + + // Handle trigger selection + const handleSelectTrigger = (trigger: { + id: string; + name: string; + type: string; + icon?: string; + }) => { + const timestamp = Date.now(); const placeholderId = `placeholder-${timestamp}`; const triggerNodeId = `trigger-${trigger.id}`; const edgeId = `e-${triggerNodeId}-${placeholderId}`; - + setNodes((currentNodes) => { - const placeholderNode = currentNodes.find((n) => n.type === "placeholder"); + const placeholderNode = currentNodes.find( + (n) => n.type === "placeholder" + ); if (!placeholderNode) return currentNodes; - + const triggerNode: NodeType = { - id: triggerNodeId, // ✅ Use variable + id: triggerNodeId, type: "trigger", - position: placeholderNode.position, + position: placeholderNode.position, data: { label: trigger.name, name: trigger.name, icon: trigger.icon || "⚡", - type: trigger.type, + type: trigger.type, }, }; - - const newPlaceholder: NodeType = { - id: placeholderId, // ✅ Use variable + + const newPlaceholder: NodeType = { + id: placeholderId, type: "placeholder", position: { - x: placeholderNode.position.x + 200, + x: placeholderNode.position.x + 250, y: placeholderNode.position.y, }, data: { label: "+" }, }; - + return [triggerNode, newPlaceholder]; }); - + setEdges([ { - id: edgeId, - source: triggerNodeId, // ✅ Matches trigger node - target: placeholderId, // ✅ Matches placeholder node - } + id: edgeId, + source: triggerNodeId, + target: placeholderId, + }, ]); }; -return ( + + const handleSelectAction = (action: { + id: string; + name: string; + type: string; + icon?: string; + }) => { + const timestamp = Date.now(); + const newPlaceholderId = `placeholder-${timestamp}`; + const actionNodeId = `action-${action.id}-${timestamp}`; + + setNodes((currentNodes) => { + const placeholderNode = currentNodes.find( + (n) => n.type === "placeholder" + ); + if (!placeholderNode) return currentNodes; + + const previousNodeId = edges.find( + (e) => e.target === placeholderNode.id + )?.source; + + const actionNode: NodeType = { + id: actionNodeId, + type: "action", + position: placeholderNode.position, + data: { + label: action.name, + name: action.name, + icon: action.icon || "⚙️", + type: action.type, + }, + }; + + const newPlaceholder: NodeType = { + id: newPlaceholderId, + type: "placeholder", + position: { + x: placeholderNode.position.x + 250, + y: placeholderNode.position.y, + }, + data: { label: "+" }, + }; + + const otherNodes = currentNodes.filter( + (n) => n.id !== placeholderNode.id + ); + return [...otherNodes, actionNode, newPlaceholder]; + }); + + setEdges((currentEdges) => { + const edgeToPlaceholder = currentEdges.find((e) => + e.target.startsWith("placeholder") + ); + + const updatedEdges = currentEdges.map((e) => { + if (edgeToPlaceholder && e.id === edgeToPlaceholder.id) { + return { ...e, target: actionNodeId }; + } + return e; + }); + + return [ + ...updatedEdges, + { + id: `e-${actionNodeId}-${newPlaceholderId}`, + source: actionNodeId, + target: newPlaceholderId, + }, + ]; + }); + }; + + return (
{ if (node.type === "placeholder") { - setSideBarOpen(true); + const hasTrigger = nodes.some((n) => n.type === "trigger"); + if (hasTrigger) { + setActionSidebarOpen(true); + } else { + setSidebarOpen(true); + } } }} - > - setSideBarOpen(false)} + /> + + setSidebarOpen(false)} onSelectTrigger={handleSelectTrigger} /> + + setActionSidebarOpen(false)} + onSelectAction={handleSelectAction} + />
); }; diff --git a/apps/web/app/hooks/useActions.tsx b/apps/web/app/hooks/useActions.tsx new file mode 100644 index 0000000..e6c5948 --- /dev/null +++ b/apps/web/app/hooks/useActions.tsx @@ -0,0 +1,34 @@ +import { BACKEND_URL } from "@repo/common/zod"; +import axios from "axios"; +import { useEffect, useState } from "react"; +import { AvailabeAction } from "../types/workflow.types"; + +interface userActionsPrps { + shouldFetch: boolean; +} +export const useActions = ({ shouldFetch }: userActionsPrps) => { + const [actions, setActions] = useState([]); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(""); + + useEffect(() => { + if (!shouldFetch) return; + const fetchData = async () => { + try { + setLoading(true); + const Data = await axios.get(`${BACKEND_URL}/user/getAvailableNodes`, { + withCredentials: true, + }); + console.log(JSON.stringify(Data.data)); + setActions(Data.data); + setLoading(false); + } catch (e: any) { + setError(e); + console.log("Error while fetching Data from Triggers", e); + } + }; + fetchData(); + }, [shouldFetch]); + + return { actions, loading, error }; +}; diff --git a/apps/web/app/page.tsx b/apps/web/app/page.tsx index b1367ba..354a5ce 100644 --- a/apps/web/app/page.tsx +++ b/apps/web/app/page.tsx @@ -1,14 +1,12 @@ import { getServerSession } from "next-auth"; import { AuthOptions } from "@/app/api/auth/utils/auth"; - export default async function Home() { const session = await getServerSession(AuthOptions); - - return ( + return (
- Hello world + Hello world
Hello world @@ -41,4 +39,4 @@ export default async function Home() {
); -} +} \ No newline at end of file diff --git a/apps/web/app/types/workflow.types.ts b/apps/web/app/types/workflow.types.ts index 130b2a2..21636db 100644 --- a/apps/web/app/types/workflow.types.ts +++ b/apps/web/app/types/workflow.types.ts @@ -25,5 +25,6 @@ export interface AvailabeAction { id: string; name: string; type: string; - config: JSON; + config: Record; + } diff --git a/package.json b/package.json index 43902b0..a193973 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "turbo": "^2.5.5", "typescript": "5.7.3" }, - "packageManager": "pnpm@10.24.0", + "packageManager": "pnpm@10.25.0", "engines": { "node": ">=20" }