nucleus-core-ts 0.8.108 → 0.8.109

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,11 @@
1
+ import type { Node } from "@xyflow/react";
2
+ import type { ReactElement } from "react";
3
+ interface NodePropertiesPanelProps {
4
+ node: Node;
5
+ onUpdate: (nodeId: string, data: Record<string, unknown>) => void;
6
+ onDelete: (nodeId: string) => void;
7
+ onClose: () => void;
8
+ }
9
+ export declare function NodePropertiesPanel({ node, onUpdate, onDelete, onClose, }: NodePropertiesPanelProps): ReactElement;
10
+ export {};
11
+ //# sourceMappingURL=NodePropertiesPanel.d.ts.map
@@ -0,0 +1,309 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ export function NodePropertiesPanel({ node, onUpdate, onDelete, onClose }) {
4
+ const data = node.data;
5
+ const nodeType = String(data.nodeType || "step");
6
+ const inputClass = "w-full rounded-lg border border-zinc-200 dark:border-zinc-700 bg-white dark:bg-zinc-800 px-3 py-2 text-sm text-zinc-900 dark:text-white outline-none focus:ring-2 focus:ring-blue-500";
7
+ const labelClass = "block text-xs font-medium text-zinc-500 dark:text-zinc-400 mb-1";
8
+ const sectionClass = "space-y-3";
9
+ return /*#__PURE__*/ _jsxs("div", {
10
+ className: "absolute top-0 right-0 h-full w-80 bg-white dark:bg-zinc-900 border-l border-zinc-200 dark:border-zinc-800 shadow-2xl z-20 flex flex-col",
11
+ children: [
12
+ /*#__PURE__*/ _jsxs("div", {
13
+ className: "flex items-center justify-between px-4 py-3 border-b border-zinc-200 dark:border-zinc-800",
14
+ children: [
15
+ /*#__PURE__*/ _jsx("h3", {
16
+ className: "text-sm font-semibold text-zinc-900 dark:text-white",
17
+ children: "Node Properties"
18
+ }),
19
+ /*#__PURE__*/ _jsx("button", {
20
+ type: "button",
21
+ onClick: onClose,
22
+ className: "text-zinc-400 hover:text-zinc-600 dark:hover:text-zinc-300 transition-colors",
23
+ children: /*#__PURE__*/ _jsxs("svg", {
24
+ className: "w-5 h-5",
25
+ fill: "none",
26
+ viewBox: "0 0 24 24",
27
+ stroke: "currentColor",
28
+ children: [
29
+ /*#__PURE__*/ _jsx("title", {
30
+ children: "Close"
31
+ }),
32
+ /*#__PURE__*/ _jsx("path", {
33
+ strokeLinecap: "round",
34
+ strokeLinejoin: "round",
35
+ strokeWidth: 2,
36
+ d: "M6 18L18 6M6 6l12 12"
37
+ })
38
+ ]
39
+ })
40
+ })
41
+ ]
42
+ }),
43
+ /*#__PURE__*/ _jsxs("div", {
44
+ className: "flex-1 overflow-auto p-4 space-y-5",
45
+ children: [
46
+ /*#__PURE__*/ _jsx("div", {
47
+ className: sectionClass,
48
+ children: /*#__PURE__*/ _jsxs("div", {
49
+ children: [
50
+ /*#__PURE__*/ _jsx("label", {
51
+ htmlFor: `node-type-${node.id}`,
52
+ className: labelClass,
53
+ children: "Type"
54
+ }),
55
+ /*#__PURE__*/ _jsxs("select", {
56
+ id: `node-type-${node.id}`,
57
+ className: inputClass,
58
+ value: nodeType,
59
+ onChange: (e)=>onUpdate(node.id, {
60
+ ...data,
61
+ nodeType: e.target.value
62
+ }),
63
+ children: [
64
+ /*#__PURE__*/ _jsx("option", {
65
+ value: "step",
66
+ children: "Step"
67
+ }),
68
+ /*#__PURE__*/ _jsx("option", {
69
+ value: "verifier",
70
+ children: "Verifier"
71
+ }),
72
+ /*#__PURE__*/ _jsx("option", {
73
+ value: "notification",
74
+ children: "Notification"
75
+ })
76
+ ]
77
+ })
78
+ ]
79
+ })
80
+ }),
81
+ /*#__PURE__*/ _jsx("div", {
82
+ className: sectionClass,
83
+ children: /*#__PURE__*/ _jsxs("div", {
84
+ children: [
85
+ /*#__PURE__*/ _jsx("label", {
86
+ htmlFor: `node-name-${node.id}`,
87
+ className: labelClass,
88
+ children: "Name"
89
+ }),
90
+ /*#__PURE__*/ _jsx("input", {
91
+ id: `node-name-${node.id}`,
92
+ type: "text",
93
+ className: inputClass,
94
+ value: String(data.label || ""),
95
+ onChange: (e)=>onUpdate(node.id, {
96
+ ...data,
97
+ label: e.target.value
98
+ }),
99
+ placeholder: "Node name"
100
+ })
101
+ ]
102
+ })
103
+ }),
104
+ /*#__PURE__*/ _jsx("div", {
105
+ className: sectionClass,
106
+ children: /*#__PURE__*/ _jsxs("div", {
107
+ children: [
108
+ /*#__PURE__*/ _jsx("label", {
109
+ htmlFor: `node-desc-${node.id}`,
110
+ className: labelClass,
111
+ children: "Description"
112
+ }),
113
+ /*#__PURE__*/ _jsx("textarea", {
114
+ id: `node-desc-${node.id}`,
115
+ className: `${inputClass} resize-none`,
116
+ rows: 3,
117
+ value: String(data.description || ""),
118
+ onChange: (e)=>onUpdate(node.id, {
119
+ ...data,
120
+ description: e.target.value
121
+ }),
122
+ placeholder: "Node description"
123
+ })
124
+ ]
125
+ })
126
+ }),
127
+ /*#__PURE__*/ _jsx("div", {
128
+ className: sectionClass,
129
+ children: /*#__PURE__*/ _jsxs("div", {
130
+ children: [
131
+ /*#__PURE__*/ _jsx("label", {
132
+ htmlFor: `node-order-${node.id}`,
133
+ className: labelClass,
134
+ children: "Step Order"
135
+ }),
136
+ /*#__PURE__*/ _jsx("input", {
137
+ id: `node-order-${node.id}`,
138
+ type: "number",
139
+ className: inputClass,
140
+ value: Number(data.stepOrder || 0),
141
+ onChange: (e)=>onUpdate(node.id, {
142
+ ...data,
143
+ stepOrder: Number.parseInt(e.target.value, 10) || 0
144
+ }),
145
+ min: 0
146
+ })
147
+ ]
148
+ })
149
+ }),
150
+ nodeType === "verifier" && /*#__PURE__*/ _jsxs("div", {
151
+ className: sectionClass,
152
+ children: [
153
+ /*#__PURE__*/ _jsxs("div", {
154
+ children: [
155
+ /*#__PURE__*/ _jsx("label", {
156
+ htmlFor: `node-vtype-${node.id}`,
157
+ className: labelClass,
158
+ children: "Verifier Type"
159
+ }),
160
+ /*#__PURE__*/ _jsxs("select", {
161
+ id: `node-vtype-${node.id}`,
162
+ className: inputClass,
163
+ value: String(data.verifierType || "role"),
164
+ onChange: (e)=>onUpdate(node.id, {
165
+ ...data,
166
+ verifierType: e.target.value
167
+ }),
168
+ children: [
169
+ /*#__PURE__*/ _jsx("option", {
170
+ value: "role",
171
+ children: "Role"
172
+ }),
173
+ /*#__PURE__*/ _jsx("option", {
174
+ value: "user",
175
+ children: "Specific User"
176
+ })
177
+ ]
178
+ })
179
+ ]
180
+ }),
181
+ /*#__PURE__*/ _jsxs("div", {
182
+ children: [
183
+ /*#__PURE__*/ _jsx("label", {
184
+ htmlFor: `node-vval-${node.id}`,
185
+ className: labelClass,
186
+ children: String(data.verifierType || "role") === "role" ? "Role Name" : "User ID"
187
+ }),
188
+ /*#__PURE__*/ _jsx("input", {
189
+ id: `node-vval-${node.id}`,
190
+ type: "text",
191
+ className: inputClass,
192
+ value: String(data.verifierValue || ""),
193
+ onChange: (e)=>onUpdate(node.id, {
194
+ ...data,
195
+ verifierValue: e.target.value
196
+ }),
197
+ placeholder: String(data.verifierType || "role") === "role" ? "e.g. manager" : "e.g. user-uuid"
198
+ })
199
+ ]
200
+ }),
201
+ /*#__PURE__*/ _jsxs("div", {
202
+ className: "flex items-center gap-2",
203
+ children: [
204
+ /*#__PURE__*/ _jsx("input", {
205
+ type: "checkbox",
206
+ id: `node-sig-${node.id}`,
207
+ checked: Boolean(data.requireSignature),
208
+ onChange: (e)=>onUpdate(node.id, {
209
+ ...data,
210
+ requireSignature: e.target.checked
211
+ }),
212
+ className: "rounded border-zinc-300 dark:border-zinc-600"
213
+ }),
214
+ /*#__PURE__*/ _jsx("label", {
215
+ htmlFor: `node-sig-${node.id}`,
216
+ className: "text-xs text-zinc-700 dark:text-zinc-300",
217
+ children: "Require Signature"
218
+ })
219
+ ]
220
+ })
221
+ ]
222
+ }),
223
+ nodeType === "notification" && /*#__PURE__*/ _jsxs("div", {
224
+ className: sectionClass,
225
+ children: [
226
+ /*#__PURE__*/ _jsxs("div", {
227
+ children: [
228
+ /*#__PURE__*/ _jsx("label", {
229
+ htmlFor: `node-channel-${node.id}`,
230
+ className: labelClass,
231
+ children: "Channel"
232
+ }),
233
+ /*#__PURE__*/ _jsxs("select", {
234
+ id: `node-channel-${node.id}`,
235
+ className: inputClass,
236
+ value: String(data.channel || "portal"),
237
+ onChange: (e)=>onUpdate(node.id, {
238
+ ...data,
239
+ channel: e.target.value
240
+ }),
241
+ children: [
242
+ /*#__PURE__*/ _jsx("option", {
243
+ value: "portal",
244
+ children: "Portal"
245
+ }),
246
+ /*#__PURE__*/ _jsx("option", {
247
+ value: "email",
248
+ children: "Email"
249
+ }),
250
+ /*#__PURE__*/ _jsx("option", {
251
+ value: "both",
252
+ children: "Email + Portal"
253
+ })
254
+ ]
255
+ })
256
+ ]
257
+ }),
258
+ /*#__PURE__*/ _jsxs("div", {
259
+ children: [
260
+ /*#__PURE__*/ _jsx("label", {
261
+ htmlFor: `node-tpl-${node.id}`,
262
+ className: labelClass,
263
+ children: "Template"
264
+ }),
265
+ /*#__PURE__*/ _jsx("input", {
266
+ id: `node-tpl-${node.id}`,
267
+ type: "text",
268
+ className: inputClass,
269
+ value: String(data.template || ""),
270
+ onChange: (e)=>onUpdate(node.id, {
271
+ ...data,
272
+ template: e.target.value
273
+ }),
274
+ placeholder: "Notification template"
275
+ })
276
+ ]
277
+ })
278
+ ]
279
+ }),
280
+ /*#__PURE__*/ _jsxs("div", {
281
+ className: "flex items-center gap-2 text-xs text-zinc-400 dark:text-zinc-500 pt-2 border-t border-zinc-100 dark:border-zinc-800",
282
+ children: [
283
+ /*#__PURE__*/ _jsx("span", {
284
+ children: "Position:"
285
+ }),
286
+ /*#__PURE__*/ _jsxs("span", {
287
+ children: [
288
+ "x: ",
289
+ Math.round(node.position.x),
290
+ ", y: ",
291
+ Math.round(node.position.y)
292
+ ]
293
+ })
294
+ ]
295
+ })
296
+ ]
297
+ }),
298
+ /*#__PURE__*/ _jsx("div", {
299
+ className: "px-4 py-3 border-t border-zinc-200 dark:border-zinc-800",
300
+ children: /*#__PURE__*/ _jsx("button", {
301
+ type: "button",
302
+ onClick: ()=>onDelete(node.id),
303
+ className: "w-full rounded-lg bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 px-3 py-2 text-xs font-medium text-red-600 dark:text-red-400 hover:bg-red-100 dark:hover:bg-red-900/30 transition-colors",
304
+ children: "Delete Node"
305
+ })
306
+ })
307
+ ]
308
+ });
309
+ }
@@ -1,5 +1,5 @@
1
1
  import "@xyflow/react/dist/style.css";
2
2
  import type { ReactElement } from "react";
3
3
  import type { VerificationFlowPageProps } from "../types";
4
- export declare function VerificationFlowPage({ entityName, title, subtitle, flowListAction, flowGetAction, pendingAction, decideAction, onFlowSelected, onDecisionMade, showPending, className, }: VerificationFlowPageProps): ReactElement;
4
+ export declare function VerificationFlowPage({ entityName, title, subtitle, flowListAction, flowGetAction, flowSaveAction, pendingAction, decideAction, onFlowSelected, onDecisionMade, showPending, className, }: VerificationFlowPageProps): ReactElement;
5
5
  //# sourceMappingURL=VerificationFlowPage.d.ts.map
@@ -1,18 +1,37 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { ReactFlow, Background, Controls, MiniMap, MarkerType } from "@xyflow/react";
3
+ import { ReactFlow, Background, Controls, MiniMap, applyNodeChanges, applyEdgeChanges, addEdge, MarkerType } from "@xyflow/react";
4
4
  import "@xyflow/react/dist/style.css";
5
5
  import { useEffect, useEffectEvent, useState } from "react";
6
6
  import { cn } from "../../../utils/cn";
7
7
  import { useVerificationFlowStore } from "../store";
8
8
  import { verificationFlowPageTheme } from "../theme";
9
9
  import { FlowNode } from "./FlowNode";
10
+ import { NodePropertiesPanel } from "./NodePropertiesPanel";
10
11
  import { PendingTab } from "./PendingTab";
11
12
  const nodeTypes = {
12
13
  step: FlowNode,
13
14
  verifier: FlowNode,
14
15
  notification: FlowNode
15
16
  };
17
+ const defaultEdgeOptions = {
18
+ type: "smoothstep",
19
+ animated: true,
20
+ markerEnd: {
21
+ type: MarkerType.ArrowClosed,
22
+ width: 16,
23
+ height: 16
24
+ },
25
+ style: {
26
+ stroke: "#94a3b8",
27
+ strokeWidth: 2
28
+ }
29
+ };
30
+ let nodeIdCounter = 0;
31
+ function nextNodeId() {
32
+ nodeIdCounter += 1;
33
+ return `node_${Date.now()}_${nodeIdCounter}`;
34
+ }
16
35
  function stepsToNodes(steps) {
17
36
  return steps.map((s)=>({
18
37
  id: s.node_id,
@@ -42,18 +61,8 @@ function edgesToFlow(flowEdges) {
42
61
  target: e.target_node_id,
43
62
  sourceHandle: e.source_handle || undefined,
44
63
  targetHandle: e.target_handle || undefined,
45
- type: "smoothstep",
46
- animated: e.animated ?? true,
47
- label: e.label || undefined,
48
- markerEnd: {
49
- type: MarkerType.ArrowClosed,
50
- width: 16,
51
- height: 16
52
- },
53
- style: {
54
- stroke: "#94a3b8",
55
- strokeWidth: 2
56
- }
64
+ ...defaultEdgeOptions,
65
+ label: e.label || undefined
57
66
  }));
58
67
  }
59
68
  function generateDemoFlow() {
@@ -155,57 +164,44 @@ function generateDemoFlow() {
155
164
  }
156
165
  }
157
166
  ];
158
- const edgeStyle = {
159
- type: "smoothstep",
160
- animated: true,
161
- markerEnd: {
162
- type: MarkerType.ArrowClosed,
163
- width: 16,
164
- height: 16
165
- },
166
- style: {
167
- stroke: "#94a3b8",
168
- strokeWidth: 2
169
- }
170
- };
171
167
  const edges = [
172
168
  {
173
169
  id: "e1",
174
170
  source: "start",
175
171
  target: "review",
176
- ...edgeStyle
172
+ ...defaultEdgeOptions
177
173
  },
178
174
  {
179
175
  id: "e2",
180
176
  source: "review",
181
177
  target: "reviewer1",
182
178
  label: "Assign",
183
- ...edgeStyle
179
+ ...defaultEdgeOptions
184
180
  },
185
181
  {
186
182
  id: "e3",
187
183
  source: "review",
188
184
  target: "reviewer2",
189
185
  label: "Assign",
190
- ...edgeStyle
186
+ ...defaultEdgeOptions
191
187
  },
192
188
  {
193
189
  id: "e4",
194
190
  source: "reviewer1",
195
191
  target: "notify",
196
- ...edgeStyle
192
+ ...defaultEdgeOptions
197
193
  },
198
194
  {
199
195
  id: "e5",
200
196
  source: "reviewer2",
201
197
  target: "notify",
202
- ...edgeStyle
198
+ ...defaultEdgeOptions
203
199
  },
204
200
  {
205
201
  id: "e6",
206
202
  source: "notify",
207
203
  target: "complete",
208
- ...edgeStyle
204
+ ...defaultEdgeOptions
209
205
  }
210
206
  ];
211
207
  return {
@@ -213,13 +209,127 @@ function generateDemoFlow() {
213
209
  edges
214
210
  };
215
211
  }
216
- export function VerificationFlowPage({ entityName, title, subtitle, flowListAction, flowGetAction, pendingAction, decideAction, onFlowSelected, onDecisionMade, showPending = true, className }) {
212
+ function createNewNode(nodeType, position) {
213
+ const labels = {
214
+ step: "New Step",
215
+ verifier: "New Verifier",
216
+ notification: "New Notification"
217
+ };
218
+ return {
219
+ id: nextNodeId(),
220
+ type: nodeType,
221
+ position,
222
+ data: {
223
+ label: labels[nodeType] || "New Node",
224
+ description: "",
225
+ nodeType,
226
+ stepOrder: 0,
227
+ decision: "",
228
+ isActive: false
229
+ }
230
+ };
231
+ }
232
+ export function VerificationFlowPage({ entityName, title, subtitle, flowListAction, flowGetAction, flowSaveAction, pendingAction, decideAction, onFlowSelected, onDecisionMade, showPending = true, className }) {
217
233
  const theme = verificationFlowPageTheme;
218
234
  const store = useVerificationFlowStore();
219
235
  const [nodes, setNodes] = useState([]);
220
236
  const [edges, setEdges] = useState([]);
221
237
  const resolvedTitle = title || `${entityName} Verification Flow`;
222
238
  const resolvedSubtitle = subtitle || `Verification workflow for the ${entityName} entity`;
239
+ const onNodesChange = useEffectEvent((changes)=>{
240
+ setNodes((prev)=>applyNodeChanges(changes, prev));
241
+ store.setIsDirty(true);
242
+ });
243
+ const onEdgesChange = useEffectEvent((changes)=>{
244
+ setEdges((prev)=>applyEdgeChanges(changes, prev));
245
+ store.setIsDirty(true);
246
+ });
247
+ const onConnect = useEffectEvent((connection)=>{
248
+ setEdges((prev)=>addEdge({
249
+ ...connection,
250
+ ...defaultEdgeOptions
251
+ }, prev));
252
+ store.setIsDirty(true);
253
+ });
254
+ const onNodeClick = useEffectEvent((_event, node)=>{
255
+ store.setSelectedNodeId(node.id);
256
+ });
257
+ const onPaneClick = useEffectEvent(()=>{
258
+ store.setSelectedNodeId(null);
259
+ });
260
+ const handleAddNode = useEffectEvent((nodeType)=>{
261
+ const position = {
262
+ x: 250 + Math.random() * 100,
263
+ y: 300 + Math.random() * 100
264
+ };
265
+ const newNode = createNewNode(nodeType, position);
266
+ setNodes((prev)=>[
267
+ ...prev,
268
+ newNode
269
+ ]);
270
+ store.setSelectedNodeId(newNode.id);
271
+ store.setIsDirty(true);
272
+ });
273
+ const handleUpdateNodeData = useEffectEvent((nodeId, newData)=>{
274
+ setNodes((prev)=>prev.map((n)=>{
275
+ if (n.id !== nodeId) return n;
276
+ const updatedType = String(newData.nodeType || n.type);
277
+ return {
278
+ ...n,
279
+ type: updatedType,
280
+ data: newData
281
+ };
282
+ }));
283
+ store.setIsDirty(true);
284
+ });
285
+ const handleDeleteNode = useEffectEvent((nodeId)=>{
286
+ setNodes((prev)=>prev.filter((n)=>n.id !== nodeId));
287
+ setEdges((prev)=>prev.filter((e)=>e.source !== nodeId && e.target !== nodeId));
288
+ store.setSelectedNodeId(null);
289
+ store.setIsDirty(true);
290
+ });
291
+ const handleSave = useEffectEvent(()=>{
292
+ if (!flowSaveAction || !store.selectedFlowId) return;
293
+ store.setIsSaving(true);
294
+ const graphNodes = nodes.map((n)=>({
295
+ node_id: n.id,
296
+ node_type: String(n.data.nodeType || n.type),
297
+ step_order: Number(n.data.stepOrder || 0),
298
+ name: String(n.data.label || ""),
299
+ description: String(n.data.description || ""),
300
+ position_x: Math.round(n.position.x),
301
+ position_y: Math.round(n.position.y),
302
+ width: n.width || undefined,
303
+ height: n.height || undefined,
304
+ data: n.data
305
+ }));
306
+ const graphEdges = edges.map((e)=>({
307
+ edge_id: e.id,
308
+ source_node_id: e.source,
309
+ target_node_id: e.target,
310
+ source_handle: e.sourceHandle || undefined,
311
+ target_handle: e.targetHandle || undefined,
312
+ edge_type: e.type || "smoothstep",
313
+ label: String(e.label || ""),
314
+ animated: true
315
+ }));
316
+ flowSaveAction.start({
317
+ payload: {
318
+ flow_id: store.selectedFlowId,
319
+ graph: {
320
+ steps: graphNodes,
321
+ edges: graphEdges
322
+ }
323
+ },
324
+ onAfterHandle: ()=>{
325
+ store.setIsSaving(false);
326
+ store.setIsDirty(false);
327
+ },
328
+ onErrorHandle: ()=>{
329
+ store.setIsSaving(false);
330
+ }
331
+ });
332
+ });
223
333
  const loadFlows = useEffectEvent(()=>{
224
334
  store.setLoadingFlows(true);
225
335
  const fallback = ()=>{
@@ -268,6 +378,7 @@ export function VerificationFlowPage({ entityName, title, subtitle, flowListActi
268
378
  setEdges(demo.edges);
269
379
  }
270
380
  store.setLoadingGraph(false);
381
+ store.setIsDirty(false);
271
382
  },
272
383
  onErrorHandle: ()=>{
273
384
  store.setLoadingGraph(false);
@@ -285,6 +396,7 @@ export function VerificationFlowPage({ entityName, title, subtitle, flowListActi
285
396
  store.selectedFlowId
286
397
  ]);
287
398
  const selectedFlow = store.flows.find((f)=>f.id === store.selectedFlowId);
399
+ const selectedNode = store.selectedNodeId ? nodes.find((n)=>n.id === store.selectedNodeId) || null : null;
288
400
  return /*#__PURE__*/ _jsx("div", {
289
401
  className: cn(theme.container.base, className),
290
402
  children: /*#__PURE__*/ _jsxs("div", {
@@ -327,7 +439,7 @@ export function VerificationFlowPage({ entityName, title, subtitle, flowListActi
327
439
  })
328
440
  ]
329
441
  }),
330
- store.flows.length > 0 && /*#__PURE__*/ _jsx("select", {
442
+ store.activeTab === "flow" && store.flows.length > 0 && /*#__PURE__*/ _jsx("select", {
331
443
  className: theme.flowSelector.select,
332
444
  value: store.selectedFlowId || "",
333
445
  onChange: (e)=>{
@@ -342,6 +454,13 @@ export function VerificationFlowPage({ entityName, title, subtitle, flowListActi
342
454
  f.is_draft ? "(Draft)" : ""
343
455
  ]
344
456
  }, f.id))
457
+ }),
458
+ store.activeTab === "flow" && flowSaveAction && /*#__PURE__*/ _jsx("button", {
459
+ type: "button",
460
+ onClick: handleSave,
461
+ disabled: store.isSaving || !store.isDirty,
462
+ className: cn("rounded-lg px-3 py-1.5 text-xs font-medium transition-colors", store.isDirty ? "bg-blue-600 text-white hover:bg-blue-700" : "bg-zinc-200 dark:bg-zinc-700 text-zinc-400 dark:text-zinc-500 cursor-not-allowed"),
463
+ children: store.isSaving ? "Saving..." : "Save"
345
464
  })
346
465
  ]
347
466
  })
@@ -357,13 +476,108 @@ export function VerificationFlowPage({ entityName, title, subtitle, flowListActi
357
476
  className: theme.loading.spinner
358
477
  })
359
478
  }),
479
+ /*#__PURE__*/ _jsxs("div", {
480
+ className: "absolute top-3 left-1/2 -translate-x-1/2 z-10 flex items-center gap-1 bg-white/90 dark:bg-zinc-900/90 backdrop-blur-sm rounded-xl border border-zinc-200 dark:border-zinc-800 p-1.5 shadow-lg",
481
+ children: [
482
+ /*#__PURE__*/ _jsxs("button", {
483
+ type: "button",
484
+ onClick: ()=>handleAddNode("step"),
485
+ className: "flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-xs font-medium text-blue-700 dark:text-blue-300 bg-blue-50 dark:bg-blue-950/40 hover:bg-blue-100 dark:hover:bg-blue-900/40 transition-colors",
486
+ children: [
487
+ /*#__PURE__*/ _jsxs("svg", {
488
+ className: "w-3.5 h-3.5",
489
+ fill: "none",
490
+ viewBox: "0 0 24 24",
491
+ stroke: "currentColor",
492
+ children: [
493
+ /*#__PURE__*/ _jsx("title", {
494
+ children: "Add Step"
495
+ }),
496
+ /*#__PURE__*/ _jsx("path", {
497
+ strokeLinecap: "round",
498
+ strokeLinejoin: "round",
499
+ strokeWidth: 2,
500
+ d: "M12 4v16m8-8H4"
501
+ })
502
+ ]
503
+ }),
504
+ "Step"
505
+ ]
506
+ }),
507
+ /*#__PURE__*/ _jsxs("button", {
508
+ type: "button",
509
+ onClick: ()=>handleAddNode("verifier"),
510
+ className: "flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-xs font-medium text-amber-700 dark:text-amber-300 bg-amber-50 dark:bg-amber-950/40 hover:bg-amber-100 dark:hover:bg-amber-900/40 transition-colors",
511
+ children: [
512
+ /*#__PURE__*/ _jsxs("svg", {
513
+ className: "w-3.5 h-3.5",
514
+ fill: "none",
515
+ viewBox: "0 0 24 24",
516
+ stroke: "currentColor",
517
+ children: [
518
+ /*#__PURE__*/ _jsx("title", {
519
+ children: "Add Verifier"
520
+ }),
521
+ /*#__PURE__*/ _jsx("path", {
522
+ strokeLinecap: "round",
523
+ strokeLinejoin: "round",
524
+ strokeWidth: 2,
525
+ d: "M12 4v16m8-8H4"
526
+ })
527
+ ]
528
+ }),
529
+ "Verifier"
530
+ ]
531
+ }),
532
+ /*#__PURE__*/ _jsxs("button", {
533
+ type: "button",
534
+ onClick: ()=>handleAddNode("notification"),
535
+ className: "flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-xs font-medium text-purple-700 dark:text-purple-300 bg-purple-50 dark:bg-purple-950/40 hover:bg-purple-100 dark:hover:bg-purple-900/40 transition-colors",
536
+ children: [
537
+ /*#__PURE__*/ _jsxs("svg", {
538
+ className: "w-3.5 h-3.5",
539
+ fill: "none",
540
+ viewBox: "0 0 24 24",
541
+ stroke: "currentColor",
542
+ children: [
543
+ /*#__PURE__*/ _jsx("title", {
544
+ children: "Add Notification"
545
+ }),
546
+ /*#__PURE__*/ _jsx("path", {
547
+ strokeLinecap: "round",
548
+ strokeLinejoin: "round",
549
+ strokeWidth: 2,
550
+ d: "M12 4v16m8-8H4"
551
+ })
552
+ ]
553
+ }),
554
+ "Notification"
555
+ ]
556
+ })
557
+ ]
558
+ }),
360
559
  /*#__PURE__*/ _jsxs(ReactFlow, {
361
560
  nodes: nodes,
362
561
  edges: edges,
363
562
  nodeTypes: nodeTypes,
563
+ onNodesChange: onNodesChange,
564
+ onEdgesChange: onEdgesChange,
565
+ onConnect: onConnect,
566
+ onNodeClick: onNodeClick,
567
+ onPaneClick: onPaneClick,
568
+ deleteKeyCode: [
569
+ "Backspace",
570
+ "Delete"
571
+ ],
364
572
  fitView: true,
365
573
  minZoom: 0.3,
366
574
  maxZoom: 2,
575
+ snapToGrid: true,
576
+ snapGrid: [
577
+ 20,
578
+ 20
579
+ ],
580
+ defaultEdgeOptions: defaultEdgeOptions,
367
581
  proOptions: {
368
582
  hideAttribution: true
369
583
  },
@@ -385,7 +599,7 @@ export function VerificationFlowPage({ entityName, title, subtitle, flowListActi
385
599
  })
386
600
  ]
387
601
  }),
388
- selectedFlow && /*#__PURE__*/ _jsxs("div", {
602
+ selectedFlow && !selectedNode && /*#__PURE__*/ _jsxs("div", {
389
603
  className: theme.overlay.info,
390
604
  children: [
391
605
  /*#__PURE__*/ _jsx("h3", {
@@ -414,6 +628,12 @@ export function VerificationFlowPage({ entityName, title, subtitle, flowListActi
414
628
  })
415
629
  ]
416
630
  }),
631
+ selectedNode && /*#__PURE__*/ _jsx(NodePropertiesPanel, {
632
+ node: selectedNode,
633
+ onUpdate: handleUpdateNodeData,
634
+ onDelete: handleDeleteNode,
635
+ onClose: ()=>store.setSelectedNodeId(null)
636
+ }),
417
637
  /*#__PURE__*/ _jsx("div", {
418
638
  className: theme.legend.container,
419
639
  children: /*#__PURE__*/ _jsxs("div", {
@@ -1,6 +1,7 @@
1
1
  export { VerificationFlowPage } from "./components/VerificationFlowPage";
2
2
  export { PendingTab } from "./components/PendingTab";
3
3
  export { FlowNode } from "./components/FlowNode";
4
+ export { NodePropertiesPanel } from "./components/NodePropertiesPanel";
4
5
  export { useVerificationFlowStore } from "./store";
5
6
  export type { VerificationFlowPageTheme } from "./theme";
6
7
  export { verificationFlowPageTheme, extendVerificationFlowPageTheme, } from "./theme";
@@ -1,5 +1,6 @@
1
1
  export { VerificationFlowPage } from "./components/VerificationFlowPage";
2
2
  export { PendingTab } from "./components/PendingTab";
3
3
  export { FlowNode } from "./components/FlowNode";
4
+ export { NodePropertiesPanel } from "./components/NodePropertiesPanel";
4
5
  export { useVerificationFlowStore } from "./store";
5
6
  export { verificationFlowPageTheme, extendVerificationFlowPageTheme } from "./theme";
@@ -12,6 +12,9 @@ type VerificationFlowMethods = {
12
12
  setLoadingPending: (value: boolean) => void;
13
13
  setDecidingId: (id: string | null) => void;
14
14
  setDecisionReason: (reason: string) => void;
15
+ setSelectedNodeId: (id: string | null) => void;
16
+ setIsSaving: (value: boolean) => void;
17
+ setIsDirty: (value: boolean) => void;
15
18
  setError: (error: string | null) => void;
16
19
  reset: () => void;
17
20
  };
@@ -12,6 +12,9 @@ const initialState = {
12
12
  isLoadingPending: false,
13
13
  decidingId: null,
14
14
  decisionReason: "",
15
+ selectedNodeId: null,
16
+ isSaving: false,
17
+ isDirty: false,
15
18
  error: null
16
19
  };
17
20
  export const { useStore: useVerificationFlowStore } = createStore(initialState, {
@@ -48,6 +51,15 @@ export const { useStore: useVerificationFlowStore } = createStore(initialState,
48
51
  setDecisionReason: (store)=>(reason)=>{
49
52
  store.decisionReason = reason;
50
53
  },
54
+ setSelectedNodeId: (store)=>(id)=>{
55
+ store.selectedNodeId = id;
56
+ },
57
+ setIsSaving: (store)=>(value)=>{
58
+ store.isSaving = value;
59
+ },
60
+ setIsDirty: (store)=>(value)=>{
61
+ store.isDirty = value;
62
+ },
51
63
  setError: (store)=>(error)=>{
52
64
  store.error = error;
53
65
  },
@@ -64,6 +76,9 @@ export const { useStore: useVerificationFlowStore } = createStore(initialState,
64
76
  store.isLoadingPending = false;
65
77
  store.decidingId = null;
66
78
  store.decisionReason = "";
79
+ store.selectedNodeId = null;
80
+ store.isSaving = false;
81
+ store.isDirty = false;
67
82
  store.error = null;
68
83
  });
69
84
  }
@@ -106,6 +106,9 @@ export type VerificationFlowPageState = {
106
106
  isLoadingPending: boolean;
107
107
  decidingId: string | null;
108
108
  decisionReason: string;
109
+ selectedNodeId: string | null;
110
+ isSaving: boolean;
111
+ isDirty: boolean;
109
112
  error: string | null;
110
113
  };
111
114
  //# sourceMappingURL=index.d.ts.map
@@ -39,7 +39,7 @@ export { NotificationBell, NotificationCenter, notificationCenterTheme, extendNo
39
39
  export type { PendingItem, PendingRequirement, PendingVerificationsConfig, PendingVerificationsProps, PendingVerificationsState, VerificationDecideAction, VerificationFlow, VerificationInstance, VerificationPanelConfig, VerificationPanelProps, VerificationPanelState, VerificationPanelVariant, VerificationPendingAction, VerificationRecord, VerificationStartAction, VerificationStatusAction, } from "./components/VerificationPanel";
40
40
  export { PendingVerifications, VerificationPanel, verificationPanelTheme, extendVerificationPanelTheme, useVerificationPanelStore, usePendingVerificationsStore, } from "./components/VerificationPanel";
41
41
  export type { FlowDeleteAction, FlowEdgeData, FlowGetAction, FlowItemData, FlowListAction, FlowPublishAction, FlowSaveAction, StepNodeData, VerificationFlowDecideAction, VerificationFlowGenericAction, VerificationFlowPageConfig, VerificationFlowPageProps, VerificationFlowPageState, VerificationFlowPageVariant, VerificationFlowPendingAction, VerificationFlowPendingItem, VerificationFlowStartAction, } from "./components/VerificationFlowPage";
42
- export { FlowNode, PendingTab, VerificationFlowPage, verificationFlowPageTheme, extendVerificationFlowPageTheme, useVerificationFlowStore, } from "./components/VerificationFlowPage";
42
+ export { FlowNode, NodePropertiesPanel, PendingTab, VerificationFlowPage, verificationFlowPageTheme, extendVerificationFlowPageTheme, useVerificationFlowStore, } from "./components/VerificationFlowPage";
43
43
  export { useNucleusEntity } from "./hooks/useNucleusEntity";
44
44
  export type { BulkDeleteResponse, ColumnConfig, ColumnEnum, ColumnReference, ColumnType, ColumnValidation, DeleteResponse, FieldConfig, FilterCondition, ListResponse, MutationResponse, NucleusColumn, NucleusEntity, NucleusEntityShowcaseProps, PaginationMeta, QueryParams, SingleResponse, SortCondition, SortDirection, UseNucleusEntityOptions, UseNucleusEntityReturn, } from "./types";
45
45
  export { cn } from "./utils/cn";
package/dist/fe/index.js CHANGED
@@ -24,7 +24,7 @@ export { UsersPage, useUsersStore } from "./components/UsersPage";
24
24
  export { useVerifyEmailStore, VerifyEmailPage } from "./components/VerifyEmailPage";
25
25
  export { NotificationBell, NotificationCenter, notificationCenterTheme, extendNotificationCenterTheme, useNotificationCenterStore } from "./components/NotificationCenter";
26
26
  export { PendingVerifications, VerificationPanel, verificationPanelTheme, extendVerificationPanelTheme, useVerificationPanelStore, usePendingVerificationsStore } from "./components/VerificationPanel";
27
- export { FlowNode, PendingTab, VerificationFlowPage, verificationFlowPageTheme, extendVerificationFlowPageTheme, useVerificationFlowStore } from "./components/VerificationFlowPage";
27
+ export { FlowNode, NodePropertiesPanel, PendingTab, VerificationFlowPage, verificationFlowPageTheme, extendVerificationFlowPageTheme, useVerificationFlowStore } from "./components/VerificationFlowPage";
28
28
  export { useNucleusEntity } from "./hooks/useNucleusEntity";
29
29
  export { cn } from "./utils/cn";
30
30
  export { formatLabel, getColumnType, getDefaultValue, isArrayColumn, isJsonColumn, isReferenceColumn, shouldExcludeColumn, shouldExcludeFromForm } from "./utils/columnUtils";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nucleus-core-ts",
3
- "version": "0.8.108",
3
+ "version": "0.8.109",
4
4
  "description": "Production-ready, enterprise-grade TypeScript framework for building multi-tenant APIs",
5
5
  "author": "Hidayet Can Özcan <hidayetcan@gmail.com>",
6
6
  "license": "SEE LICENSE IN LICENSE",