@inspirer-dev/crm-dashboard 1.0.21 → 1.0.23

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.
Files changed (33) hide show
  1. package/admin/src/components/StepFlowBuilder/constants.ts +91 -0
  2. package/admin/src/components/StepFlowBuilder/edges/LabeledEdge.tsx +77 -0
  3. package/admin/src/components/StepFlowBuilder/edges/index.ts +8 -0
  4. package/admin/src/components/StepFlowBuilder/index.tsx +320 -0
  5. package/admin/src/components/StepFlowBuilder/nodes/BranchNode.tsx +90 -0
  6. package/admin/src/components/StepFlowBuilder/nodes/EntryNode.tsx +47 -0
  7. package/admin/src/components/StepFlowBuilder/nodes/ExitNode.tsx +47 -0
  8. package/admin/src/components/StepFlowBuilder/nodes/MessageNode.tsx +78 -0
  9. package/admin/src/components/StepFlowBuilder/nodes/WaitNode.tsx +71 -0
  10. package/admin/src/components/StepFlowBuilder/nodes/index.ts +16 -0
  11. package/admin/src/components/StepFlowBuilder/panels/BranchConfig.tsx +112 -0
  12. package/admin/src/components/StepFlowBuilder/panels/MessageConfig.tsx +188 -0
  13. package/admin/src/components/StepFlowBuilder/panels/NodeEditPanel.tsx +158 -0
  14. package/admin/src/components/StepFlowBuilder/panels/WaitConfig.tsx +87 -0
  15. package/admin/src/components/StepFlowBuilder/panels/index.ts +4 -0
  16. package/admin/src/components/StepFlowBuilder/toolbar/FlowToolbar.tsx +86 -0
  17. package/admin/src/components/StepFlowBuilder/toolbar/index.ts +1 -0
  18. package/admin/src/components/StepFlowBuilder/types.ts +77 -0
  19. package/admin/src/components/StepFlowBuilder/utils.ts +217 -0
  20. package/admin/src/index.ts +8 -8
  21. package/dist/_chunks/index-BK8649hk.mjs +1405 -0
  22. package/dist/_chunks/{index-CWnuAWMG.mjs → index-BeiHTAlq.mjs} +91 -112
  23. package/dist/_chunks/{index-Bw1mkNpv.js → index-XoiSAQhK.js} +91 -112
  24. package/dist/_chunks/index-aSjgyfVX.js +1408 -0
  25. package/dist/admin/index.js +9 -9
  26. package/dist/admin/index.mjs +10 -10
  27. package/dist/server/index.js +1 -1
  28. package/dist/server/index.mjs +1 -1
  29. package/package.json +3 -1
  30. package/server/src/register.ts +1 -1
  31. package/admin/src/components/JourneyConfigField/index.tsx +0 -744
  32. package/dist/_chunks/index-BhNY5vYI.js +0 -591
  33. package/dist/_chunks/index-D0XEcc24.mjs +0 -591
@@ -0,0 +1,1405 @@
1
+ import { jsxs, Fragment, jsx } from "react/jsx-runtime";
2
+ import { memo, useState, useEffect, forwardRef, useRef, useCallback, useMemo } from "react";
3
+ import ReactFlow, { Handle, Position, getBezierPath, BaseEdge, EdgeLabelRenderer, ReactFlowProvider, useNodesState, useEdgesState, addEdge, Background, Controls, MiniMap } from "reactflow";
4
+ import "reactflow/dist/style.css";
5
+ import { Flex, Typography, Badge, Box, SingleSelect, SingleSelectOption, Button, TextInput, NumberInput, IconButton, Loader, Divider, Tooltip, Field } from "@strapi/design-system";
6
+ import { Play, Message, Clock, ArrowRight, Cross, Plus, Trash, Layout } from "@strapi/icons";
7
+ import { useFetchClient } from "@strapi/strapi/admin";
8
+ import dagre from "dagre";
9
+ const NODE_COLORS = {
10
+ entry: {
11
+ background: "#eef5eb",
12
+ border: "#5cb176",
13
+ text: "#297c3b"
14
+ },
15
+ message: {
16
+ background: "#eef8ff",
17
+ border: "#0077cc",
18
+ text: "#0055a4"
19
+ },
20
+ wait: {
21
+ background: "#fffae6",
22
+ border: "#e9b200",
23
+ text: "#a16207"
24
+ },
25
+ branch: {
26
+ background: "#f5f0ff",
27
+ border: "#7b61ff",
28
+ text: "#5746af"
29
+ },
30
+ exit: {
31
+ background: "#fef1f0",
32
+ border: "#dc2626",
33
+ text: "#c03030"
34
+ }
35
+ };
36
+ const NODE_DIMENSIONS = {
37
+ width: 220,
38
+ height: 80
39
+ };
40
+ const STEP_TYPE_LABELS = {
41
+ entry: "Entry Point",
42
+ message: "Send Message",
43
+ wait: "Wait",
44
+ branch: "Branch",
45
+ exit: "Exit"
46
+ };
47
+ const CHANNEL_OPTIONS = [
48
+ { value: "telegram", label: "Telegram" },
49
+ { value: "email", label: "Email" },
50
+ { value: "push", label: "Push Notification" },
51
+ { value: "sms", label: "SMS" }
52
+ ];
53
+ const DURATION_UNIT_OPTIONS = [
54
+ { value: "minutes", label: "Minutes" },
55
+ { value: "hours", label: "Hours" },
56
+ { value: "days", label: "Days" }
57
+ ];
58
+ const DEFAULT_NODE_DATA = {
59
+ entry: {
60
+ name: "Entry",
61
+ config: {}
62
+ },
63
+ message: {
64
+ name: "Send Message",
65
+ config: {
66
+ channel: "telegram",
67
+ variants: []
68
+ }
69
+ },
70
+ wait: {
71
+ name: "Wait",
72
+ config: {
73
+ duration: 1,
74
+ durationUnit: "hours"
75
+ }
76
+ },
77
+ branch: {
78
+ name: "Branch",
79
+ config: {
80
+ branchSegment: null
81
+ }
82
+ },
83
+ exit: {
84
+ name: "Exit",
85
+ config: {}
86
+ }
87
+ };
88
+ const ENTRY_NODE_ID = "entry";
89
+ const EntryNode = ({ selected }) => {
90
+ const colors = NODE_COLORS.entry;
91
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
92
+ /* @__PURE__ */ jsxs(
93
+ Flex,
94
+ {
95
+ alignItems: "center",
96
+ justifyContent: "center",
97
+ gap: 2,
98
+ padding: 3,
99
+ background: "neutral0",
100
+ hasRadius: true,
101
+ style: {
102
+ width: NODE_DIMENSIONS.width,
103
+ height: NODE_DIMENSIONS.height,
104
+ border: `2px solid ${selected ? colors.border : colors.background}`,
105
+ backgroundColor: colors.background,
106
+ boxShadow: selected ? `0 0 0 2px ${colors.border}` : "none"
107
+ },
108
+ children: [
109
+ /* @__PURE__ */ jsx(Play, { style: { color: colors.text, width: 20, height: 20 } }),
110
+ /* @__PURE__ */ jsx(Typography, { variant: "omega", fontWeight: "semiBold", style: { color: colors.text }, children: "Entry Point" })
111
+ ]
112
+ }
113
+ ),
114
+ /* @__PURE__ */ jsx(
115
+ Handle,
116
+ {
117
+ type: "source",
118
+ position: Position.Bottom,
119
+ style: {
120
+ background: colors.border,
121
+ width: 10,
122
+ height: 10,
123
+ border: "2px solid white"
124
+ }
125
+ }
126
+ )
127
+ ] });
128
+ };
129
+ const EntryNode$1 = memo(EntryNode);
130
+ const MessageNode = ({ data, selected }) => {
131
+ const colors = NODE_COLORS.message;
132
+ const channel = data.config.channel || "telegram";
133
+ const variantCount = data.config.variants?.length || 0;
134
+ const channelLabel = CHANNEL_OPTIONS.find((c) => c.value === channel)?.label || channel;
135
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
136
+ /* @__PURE__ */ jsx(
137
+ Handle,
138
+ {
139
+ type: "target",
140
+ position: Position.Top,
141
+ style: {
142
+ background: colors.border,
143
+ width: 10,
144
+ height: 10,
145
+ border: "2px solid white"
146
+ }
147
+ }
148
+ ),
149
+ /* @__PURE__ */ jsxs(
150
+ Flex,
151
+ {
152
+ direction: "column",
153
+ alignItems: "center",
154
+ justifyContent: "center",
155
+ gap: 1,
156
+ padding: 3,
157
+ background: "neutral0",
158
+ hasRadius: true,
159
+ style: {
160
+ width: NODE_DIMENSIONS.width,
161
+ height: NODE_DIMENSIONS.height,
162
+ border: `2px solid ${selected ? colors.border : colors.background}`,
163
+ backgroundColor: colors.background,
164
+ boxShadow: selected ? `0 0 0 2px ${colors.border}` : "none"
165
+ },
166
+ children: [
167
+ /* @__PURE__ */ jsxs(Flex, { alignItems: "center", gap: 2, children: [
168
+ /* @__PURE__ */ jsx(Message, { style: { color: colors.text, width: 18, height: 18 } }),
169
+ /* @__PURE__ */ jsx(
170
+ Typography,
171
+ {
172
+ variant: "omega",
173
+ fontWeight: "semiBold",
174
+ style: { color: colors.text },
175
+ ellipsis: true,
176
+ children: data.name
177
+ }
178
+ )
179
+ ] }),
180
+ /* @__PURE__ */ jsxs(Flex, { gap: 1, children: [
181
+ /* @__PURE__ */ jsx(Badge, { size: "S", backgroundColor: "neutral100", textColor: "neutral700", children: channelLabel }),
182
+ variantCount > 0 && /* @__PURE__ */ jsxs(Badge, { size: "S", backgroundColor: "primary100", textColor: "primary700", children: [
183
+ variantCount,
184
+ " variant",
185
+ variantCount !== 1 ? "s" : ""
186
+ ] })
187
+ ] })
188
+ ]
189
+ }
190
+ ),
191
+ /* @__PURE__ */ jsx(
192
+ Handle,
193
+ {
194
+ type: "source",
195
+ position: Position.Bottom,
196
+ style: {
197
+ background: colors.border,
198
+ width: 10,
199
+ height: 10,
200
+ border: "2px solid white"
201
+ }
202
+ }
203
+ )
204
+ ] });
205
+ };
206
+ const MessageNode$1 = memo(MessageNode);
207
+ const WaitNode = ({ data, selected }) => {
208
+ const colors = NODE_COLORS.wait;
209
+ const duration = data.config.duration || 1;
210
+ const unit = data.config.durationUnit || "hours";
211
+ const unitLabel = DURATION_UNIT_OPTIONS.find((u) => u.value === unit)?.label.toLowerCase() || unit;
212
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
213
+ /* @__PURE__ */ jsx(
214
+ Handle,
215
+ {
216
+ type: "target",
217
+ position: Position.Top,
218
+ style: {
219
+ background: colors.border,
220
+ width: 10,
221
+ height: 10,
222
+ border: "2px solid white"
223
+ }
224
+ }
225
+ ),
226
+ /* @__PURE__ */ jsxs(
227
+ Flex,
228
+ {
229
+ direction: "column",
230
+ alignItems: "center",
231
+ justifyContent: "center",
232
+ gap: 1,
233
+ padding: 3,
234
+ background: "neutral0",
235
+ hasRadius: true,
236
+ style: {
237
+ width: NODE_DIMENSIONS.width,
238
+ height: NODE_DIMENSIONS.height,
239
+ border: `2px solid ${selected ? colors.border : colors.background}`,
240
+ backgroundColor: colors.background,
241
+ boxShadow: selected ? `0 0 0 2px ${colors.border}` : "none"
242
+ },
243
+ children: [
244
+ /* @__PURE__ */ jsxs(Flex, { alignItems: "center", gap: 2, children: [
245
+ /* @__PURE__ */ jsx(Clock, { style: { color: colors.text, width: 18, height: 18 } }),
246
+ /* @__PURE__ */ jsx(
247
+ Typography,
248
+ {
249
+ variant: "omega",
250
+ fontWeight: "semiBold",
251
+ style: { color: colors.text },
252
+ ellipsis: true,
253
+ children: data.name
254
+ }
255
+ )
256
+ ] }),
257
+ /* @__PURE__ */ jsxs(Badge, { size: "S", backgroundColor: "warning100", textColor: "warning700", children: [
258
+ duration,
259
+ " ",
260
+ unitLabel
261
+ ] })
262
+ ]
263
+ }
264
+ ),
265
+ /* @__PURE__ */ jsx(
266
+ Handle,
267
+ {
268
+ type: "source",
269
+ position: Position.Bottom,
270
+ style: {
271
+ background: colors.border,
272
+ width: 10,
273
+ height: 10,
274
+ border: "2px solid white"
275
+ }
276
+ }
277
+ )
278
+ ] });
279
+ };
280
+ const WaitNode$1 = memo(WaitNode);
281
+ const BranchNode = ({ data, selected }) => {
282
+ const colors = NODE_COLORS.branch;
283
+ const segmentName = data.config.branchSegment?.name || "No segment";
284
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
285
+ /* @__PURE__ */ jsx(
286
+ Handle,
287
+ {
288
+ type: "target",
289
+ position: Position.Top,
290
+ style: {
291
+ background: colors.border,
292
+ width: 10,
293
+ height: 10,
294
+ border: "2px solid white"
295
+ }
296
+ }
297
+ ),
298
+ /* @__PURE__ */ jsxs(
299
+ Flex,
300
+ {
301
+ direction: "column",
302
+ alignItems: "center",
303
+ justifyContent: "center",
304
+ gap: 1,
305
+ padding: 3,
306
+ background: "neutral0",
307
+ hasRadius: true,
308
+ style: {
309
+ width: NODE_DIMENSIONS.width,
310
+ height: NODE_DIMENSIONS.height,
311
+ border: `2px solid ${selected ? colors.border : colors.background}`,
312
+ backgroundColor: colors.background,
313
+ boxShadow: selected ? `0 0 0 2px ${colors.border}` : "none"
314
+ },
315
+ children: [
316
+ /* @__PURE__ */ jsxs(Flex, { alignItems: "center", gap: 2, children: [
317
+ /* @__PURE__ */ jsx(ArrowRight, { style: { color: colors.text, width: 18, height: 18 } }),
318
+ /* @__PURE__ */ jsx(
319
+ Typography,
320
+ {
321
+ variant: "omega",
322
+ fontWeight: "semiBold",
323
+ style: { color: colors.text },
324
+ ellipsis: true,
325
+ children: data.name
326
+ }
327
+ )
328
+ ] }),
329
+ /* @__PURE__ */ jsx(
330
+ Badge,
331
+ {
332
+ size: "S",
333
+ backgroundColor: "secondary100",
334
+ textColor: "secondary700",
335
+ style: { maxWidth: "90%" },
336
+ children: /* @__PURE__ */ jsx(Typography, { variant: "pi", ellipsis: true, children: segmentName })
337
+ }
338
+ )
339
+ ]
340
+ }
341
+ ),
342
+ /* @__PURE__ */ jsx(
343
+ Handle,
344
+ {
345
+ type: "source",
346
+ position: Position.Bottom,
347
+ id: "yes",
348
+ style: {
349
+ background: "#5cb176",
350
+ width: 10,
351
+ height: 10,
352
+ border: "2px solid white",
353
+ left: "30%"
354
+ }
355
+ }
356
+ ),
357
+ /* @__PURE__ */ jsx(
358
+ Handle,
359
+ {
360
+ type: "source",
361
+ position: Position.Bottom,
362
+ id: "no",
363
+ style: {
364
+ background: "#dc2626",
365
+ width: 10,
366
+ height: 10,
367
+ border: "2px solid white",
368
+ left: "70%"
369
+ }
370
+ }
371
+ )
372
+ ] });
373
+ };
374
+ const BranchNode$1 = memo(BranchNode);
375
+ const ExitNode = ({ data, selected }) => {
376
+ const colors = NODE_COLORS.exit;
377
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
378
+ /* @__PURE__ */ jsx(
379
+ Handle,
380
+ {
381
+ type: "target",
382
+ position: Position.Top,
383
+ style: {
384
+ background: colors.border,
385
+ width: 10,
386
+ height: 10,
387
+ border: "2px solid white"
388
+ }
389
+ }
390
+ ),
391
+ /* @__PURE__ */ jsxs(
392
+ Flex,
393
+ {
394
+ alignItems: "center",
395
+ justifyContent: "center",
396
+ gap: 2,
397
+ padding: 3,
398
+ background: "neutral0",
399
+ hasRadius: true,
400
+ style: {
401
+ width: NODE_DIMENSIONS.width,
402
+ height: NODE_DIMENSIONS.height,
403
+ border: `2px solid ${selected ? colors.border : colors.background}`,
404
+ backgroundColor: colors.background,
405
+ boxShadow: selected ? `0 0 0 2px ${colors.border}` : "none"
406
+ },
407
+ children: [
408
+ /* @__PURE__ */ jsx(Cross, { style: { color: colors.text, width: 18, height: 18 } }),
409
+ /* @__PURE__ */ jsx(Typography, { variant: "omega", fontWeight: "semiBold", style: { color: colors.text }, children: data.name })
410
+ ]
411
+ }
412
+ )
413
+ ] });
414
+ };
415
+ const ExitNode$1 = memo(ExitNode);
416
+ const nodeTypes = {
417
+ entry: EntryNode$1,
418
+ message: MessageNode$1,
419
+ wait: WaitNode$1,
420
+ branch: BranchNode$1,
421
+ exit: ExitNode$1
422
+ };
423
+ const LabeledEdge = ({
424
+ id,
425
+ sourceX,
426
+ sourceY,
427
+ targetX,
428
+ targetY,
429
+ sourcePosition,
430
+ targetPosition,
431
+ sourceHandle,
432
+ style = {},
433
+ markerEnd
434
+ }) => {
435
+ const [edgePath, labelX, labelY] = getBezierPath({
436
+ sourceX,
437
+ sourceY,
438
+ sourcePosition,
439
+ targetX,
440
+ targetY,
441
+ targetPosition
442
+ });
443
+ const isYes = sourceHandle === "yes";
444
+ const isNo = sourceHandle === "no";
445
+ const label = isYes ? "Yes" : isNo ? "No" : null;
446
+ const bgColor = isYes ? "#dcfce7" : isNo ? "#fef2f2" : "#fff";
447
+ const textColor = isYes ? "#15803d" : isNo ? "#dc2626" : "#333";
448
+ const borderColor = isYes ? "#86efac" : isNo ? "#fca5a5" : "#ddd";
449
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
450
+ /* @__PURE__ */ jsx(
451
+ BaseEdge,
452
+ {
453
+ path: edgePath,
454
+ markerEnd,
455
+ style: {
456
+ ...style,
457
+ stroke: isYes ? "#5cb176" : isNo ? "#dc2626" : "#b1b1b7",
458
+ strokeWidth: 2
459
+ }
460
+ }
461
+ ),
462
+ label && /* @__PURE__ */ jsx(EdgeLabelRenderer, { children: /* @__PURE__ */ jsx(
463
+ "div",
464
+ {
465
+ style: {
466
+ position: "absolute",
467
+ transform: `translate(-50%, -50%) translate(${labelX}px, ${labelY}px)`,
468
+ fontSize: 11,
469
+ fontWeight: 600,
470
+ background: bgColor,
471
+ color: textColor,
472
+ padding: "2px 8px",
473
+ borderRadius: 4,
474
+ border: `1px solid ${borderColor}`,
475
+ pointerEvents: "all"
476
+ },
477
+ className: "nodrag nopan",
478
+ children: label
479
+ }
480
+ ) })
481
+ ] });
482
+ };
483
+ const edgeTypes = {
484
+ labeled: LabeledEdge
485
+ };
486
+ const MessageConfig = ({ data, onUpdate, disabled }) => {
487
+ const [variants, setVariants] = useState(data.config.variants || []);
488
+ const channel = data.config.channel || "telegram";
489
+ useEffect(() => {
490
+ setVariants(data.config.variants || []);
491
+ }, [data.config.variants]);
492
+ const handleChannelChange = (value) => {
493
+ onUpdate({
494
+ config: {
495
+ ...data.config,
496
+ channel: value
497
+ }
498
+ });
499
+ };
500
+ const handleAddVariant = () => {
501
+ const newVariant = {
502
+ name: `Variant ${variants.length + 1}`,
503
+ template: null,
504
+ weight: 100
505
+ };
506
+ const updated = [...variants, newVariant];
507
+ setVariants(updated);
508
+ onUpdate({
509
+ config: {
510
+ ...data.config,
511
+ variants: updated
512
+ }
513
+ });
514
+ };
515
+ const handleRemoveVariant = (index) => {
516
+ const updated = variants.filter((_, i) => i !== index);
517
+ setVariants(updated);
518
+ onUpdate({
519
+ config: {
520
+ ...data.config,
521
+ variants: updated
522
+ }
523
+ });
524
+ };
525
+ const handleVariantChange = (index, field, value) => {
526
+ const updated = variants.map(
527
+ (v, i) => i === index ? { ...v, [field]: value } : v
528
+ );
529
+ setVariants(updated);
530
+ onUpdate({
531
+ config: {
532
+ ...data.config,
533
+ variants: updated
534
+ }
535
+ });
536
+ };
537
+ return /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 4, children: [
538
+ /* @__PURE__ */ jsxs(Box, { children: [
539
+ /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", children: "Channel" }),
540
+ /* @__PURE__ */ jsx(Box, { paddingTop: 1, children: /* @__PURE__ */ jsx(
541
+ SingleSelect,
542
+ {
543
+ value: channel,
544
+ onChange: handleChannelChange,
545
+ disabled,
546
+ children: CHANNEL_OPTIONS.map((opt) => /* @__PURE__ */ jsx(SingleSelectOption, { value: opt.value, children: opt.label }, opt.value))
547
+ }
548
+ ) })
549
+ ] }),
550
+ /* @__PURE__ */ jsxs(Box, { children: [
551
+ /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", children: [
552
+ /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", children: "Variants (A/B Testing)" }),
553
+ /* @__PURE__ */ jsx(
554
+ Button,
555
+ {
556
+ variant: "tertiary",
557
+ size: "S",
558
+ startIcon: /* @__PURE__ */ jsx(Plus, {}),
559
+ onClick: handleAddVariant,
560
+ disabled,
561
+ children: "Add Variant"
562
+ }
563
+ )
564
+ ] }),
565
+ /* @__PURE__ */ jsx(Flex, { direction: "column", gap: 2, paddingTop: 2, children: variants.length === 0 ? /* @__PURE__ */ jsx(
566
+ Box,
567
+ {
568
+ padding: 4,
569
+ background: "neutral100",
570
+ hasRadius: true,
571
+ style: { textAlign: "center" },
572
+ children: /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral500", children: "No variants yet. Add a variant to enable A/B testing." })
573
+ }
574
+ ) : variants.map((variant, index) => /* @__PURE__ */ jsxs(
575
+ Box,
576
+ {
577
+ padding: 3,
578
+ background: "neutral100",
579
+ hasRadius: true,
580
+ style: { border: "1px solid #dcdce4" },
581
+ children: [
582
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "flex-end", children: [
583
+ /* @__PURE__ */ jsxs(Box, { style: { flex: 1 }, children: [
584
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: "Name" }),
585
+ /* @__PURE__ */ jsx(
586
+ TextInput,
587
+ {
588
+ value: variant.name,
589
+ onChange: (e) => handleVariantChange(index, "name", e.target.value),
590
+ disabled,
591
+ size: "S"
592
+ }
593
+ )
594
+ ] }),
595
+ /* @__PURE__ */ jsxs(Box, { style: { width: 80 }, children: [
596
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: "Weight" }),
597
+ /* @__PURE__ */ jsx(
598
+ NumberInput,
599
+ {
600
+ value: variant.weight,
601
+ onValueChange: (value) => handleVariantChange(index, "weight", value || 0),
602
+ disabled,
603
+ size: "S"
604
+ }
605
+ )
606
+ ] }),
607
+ /* @__PURE__ */ jsx(
608
+ IconButton,
609
+ {
610
+ onClick: () => handleRemoveVariant(index),
611
+ label: "Remove variant",
612
+ variant: "ghost",
613
+ disabled,
614
+ children: /* @__PURE__ */ jsx(Trash, {})
615
+ }
616
+ )
617
+ ] }),
618
+ /* @__PURE__ */ jsx(Box, { paddingTop: 2, children: /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "neutral500", children: [
619
+ "Template: ",
620
+ variant.template?.name || "Not selected"
621
+ ] }) })
622
+ ]
623
+ },
624
+ index
625
+ )) })
626
+ ] })
627
+ ] });
628
+ };
629
+ const WaitConfig = ({ data, onUpdate, disabled }) => {
630
+ const duration = data.config.duration || 1;
631
+ const durationUnit = data.config.durationUnit || "hours";
632
+ const handleDurationChange = (value) => {
633
+ onUpdate({
634
+ config: {
635
+ ...data.config,
636
+ duration: value || 1
637
+ }
638
+ });
639
+ };
640
+ const handleUnitChange = (value) => {
641
+ onUpdate({
642
+ config: {
643
+ ...data.config,
644
+ durationUnit: value
645
+ }
646
+ });
647
+ };
648
+ return /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 4, children: [
649
+ /* @__PURE__ */ jsxs(Box, { children: [
650
+ /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", children: "Wait Duration" }),
651
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, paddingTop: 2, children: [
652
+ /* @__PURE__ */ jsx(Box, { style: { width: 100 }, children: /* @__PURE__ */ jsx(
653
+ NumberInput,
654
+ {
655
+ value: duration,
656
+ onValueChange: handleDurationChange,
657
+ disabled,
658
+ step: 1,
659
+ label: ""
660
+ }
661
+ ) }),
662
+ /* @__PURE__ */ jsx(Box, { style: { flex: 1 }, children: /* @__PURE__ */ jsx(
663
+ SingleSelect,
664
+ {
665
+ value: durationUnit,
666
+ onChange: handleUnitChange,
667
+ disabled,
668
+ label: "",
669
+ children: DURATION_UNIT_OPTIONS.map((opt) => /* @__PURE__ */ jsx(SingleSelectOption, { value: opt.value, children: opt.label }, opt.value))
670
+ }
671
+ ) })
672
+ ] })
673
+ ] }),
674
+ /* @__PURE__ */ jsx(
675
+ Box,
676
+ {
677
+ padding: 3,
678
+ background: "primary100",
679
+ hasRadius: true,
680
+ children: /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "primary700", children: [
681
+ "Users will wait ",
682
+ duration,
683
+ " ",
684
+ durationUnit.toLowerCase(),
685
+ " before proceeding to the next step."
686
+ ] })
687
+ }
688
+ )
689
+ ] });
690
+ };
691
+ const BranchConfig = ({ data, onUpdate, disabled }) => {
692
+ const { get } = useFetchClient();
693
+ const [segments, setSegments] = useState([]);
694
+ const [loading, setLoading] = useState(true);
695
+ const selectedSegmentId = data.config.branchSegment?.id?.toString() || "";
696
+ useEffect(() => {
697
+ const fetchSegments = async () => {
698
+ try {
699
+ const response = await get("/content-manager/collection-types/api::crm-segment.crm-segment", {
700
+ params: {
701
+ pageSize: 100,
702
+ sort: "name:asc"
703
+ }
704
+ });
705
+ const results = response.data?.results || [];
706
+ setSegments(
707
+ results.map((s) => ({
708
+ id: s.id,
709
+ name: s.name
710
+ }))
711
+ );
712
+ } catch (error) {
713
+ console.error("Failed to fetch segments:", error);
714
+ } finally {
715
+ setLoading(false);
716
+ }
717
+ };
718
+ fetchSegments();
719
+ }, [get]);
720
+ const handleSegmentChange = (value) => {
721
+ const segment = segments.find((s) => s.id.toString() === value);
722
+ onUpdate({
723
+ config: {
724
+ ...data.config,
725
+ branchSegment: segment ? { id: segment.id, name: segment.name } : null
726
+ }
727
+ });
728
+ };
729
+ if (loading) {
730
+ return /* @__PURE__ */ jsx(Flex, { justifyContent: "center", padding: 4, children: /* @__PURE__ */ jsx(Loader, { small: true, children: "Loading segments..." }) });
731
+ }
732
+ return /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 4, children: [
733
+ /* @__PURE__ */ jsxs(Box, { children: [
734
+ /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", children: "Condition Segment" }),
735
+ /* @__PURE__ */ jsx(Box, { paddingTop: 1, children: /* @__PURE__ */ jsx(
736
+ SingleSelect,
737
+ {
738
+ value: selectedSegmentId,
739
+ onChange: handleSegmentChange,
740
+ disabled,
741
+ placeholder: "Select a segment...",
742
+ children: segments.map((segment) => /* @__PURE__ */ jsx(SingleSelectOption, { value: segment.id.toString(), children: segment.name }, segment.id))
743
+ }
744
+ ) })
745
+ ] }),
746
+ /* @__PURE__ */ jsx(
747
+ Box,
748
+ {
749
+ padding: 3,
750
+ background: "secondary100",
751
+ hasRadius: true,
752
+ children: /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 2, children: [
753
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "secondary700", fontWeight: "semiBold", children: "How it works:" }),
754
+ /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "secondary600", children: [
755
+ "• Users matching the segment → ",
756
+ /* @__PURE__ */ jsx("strong", { children: "Yes" }),
757
+ " path"
758
+ ] }),
759
+ /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "secondary600", children: [
760
+ "• Users not matching → ",
761
+ /* @__PURE__ */ jsx("strong", { children: "No" }),
762
+ " path"
763
+ ] })
764
+ ] })
765
+ }
766
+ )
767
+ ] });
768
+ };
769
+ const NodeEditPanel = ({
770
+ node,
771
+ onClose,
772
+ onUpdate,
773
+ disabled
774
+ }) => {
775
+ if (!node) return null;
776
+ const { stepType, name } = node.data;
777
+ const colors = NODE_COLORS[stepType];
778
+ const typeLabel = STEP_TYPE_LABELS[stepType];
779
+ const handleNameChange = (e) => {
780
+ onUpdate(node.id, { name: e.target.value });
781
+ };
782
+ const handleConfigUpdate = (updates) => {
783
+ onUpdate(node.id, updates);
784
+ };
785
+ const renderConfig = () => {
786
+ switch (stepType) {
787
+ case "message":
788
+ return /* @__PURE__ */ jsx(
789
+ MessageConfig,
790
+ {
791
+ data: node.data,
792
+ onUpdate: handleConfigUpdate,
793
+ disabled
794
+ }
795
+ );
796
+ case "wait":
797
+ return /* @__PURE__ */ jsx(
798
+ WaitConfig,
799
+ {
800
+ data: node.data,
801
+ onUpdate: handleConfigUpdate,
802
+ disabled
803
+ }
804
+ );
805
+ case "branch":
806
+ return /* @__PURE__ */ jsx(
807
+ BranchConfig,
808
+ {
809
+ data: node.data,
810
+ onUpdate: handleConfigUpdate,
811
+ disabled
812
+ }
813
+ );
814
+ case "entry":
815
+ case "exit":
816
+ default:
817
+ return /* @__PURE__ */ jsx(
818
+ Box,
819
+ {
820
+ padding: 3,
821
+ background: "neutral100",
822
+ hasRadius: true,
823
+ style: { textAlign: "center" },
824
+ children: /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral500", children: "No additional configuration needed for this step type." })
825
+ }
826
+ );
827
+ }
828
+ };
829
+ return /* @__PURE__ */ jsxs(
830
+ Box,
831
+ {
832
+ background: "neutral0",
833
+ shadow: "filterShadow",
834
+ hasRadius: true,
835
+ style: {
836
+ position: "absolute",
837
+ top: 0,
838
+ right: 0,
839
+ width: 350,
840
+ height: "100%",
841
+ zIndex: 10,
842
+ borderLeft: "1px solid #dcdce4",
843
+ overflow: "auto"
844
+ },
845
+ children: [
846
+ /* @__PURE__ */ jsx(
847
+ Box,
848
+ {
849
+ padding: 4,
850
+ style: {
851
+ borderBottom: `3px solid ${colors.border}`,
852
+ backgroundColor: colors.background
853
+ },
854
+ children: /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", children: [
855
+ /* @__PURE__ */ jsx(Flex, { alignItems: "center", gap: 2, children: /* @__PURE__ */ jsx(Typography, { variant: "beta", style: { color: colors.text }, children: typeLabel }) }),
856
+ /* @__PURE__ */ jsx(IconButton, { onClick: onClose, label: "Close panel", variant: "ghost", children: /* @__PURE__ */ jsx(Cross, {}) })
857
+ ] })
858
+ }
859
+ ),
860
+ /* @__PURE__ */ jsx(Box, { padding: 4, children: /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 4, children: [
861
+ stepType !== "entry" && /* @__PURE__ */ jsxs(Box, { children: [
862
+ /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", children: "Step Name" }),
863
+ /* @__PURE__ */ jsx(Box, { paddingTop: 1, children: /* @__PURE__ */ jsx(
864
+ TextInput,
865
+ {
866
+ value: name,
867
+ onChange: handleNameChange,
868
+ disabled: disabled || stepType === "entry",
869
+ placeholder: "Enter step name..."
870
+ }
871
+ ) })
872
+ ] }),
873
+ stepType !== "entry" && stepType !== "exit" && /* @__PURE__ */ jsxs(Fragment, { children: [
874
+ /* @__PURE__ */ jsx(Divider, {}),
875
+ renderConfig()
876
+ ] }),
877
+ stepType !== "entry" && /* @__PURE__ */ jsxs(Fragment, { children: [
878
+ /* @__PURE__ */ jsx(Divider, {}),
879
+ /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "neutral500", children: [
880
+ "Step ID: ",
881
+ node.id
882
+ ] }) })
883
+ ] })
884
+ ] }) })
885
+ ]
886
+ }
887
+ );
888
+ };
889
+ const TOOLBAR_ITEMS = [
890
+ {
891
+ type: "message",
892
+ label: "Message",
893
+ icon: /* @__PURE__ */ jsx(Message, { style: { width: 16, height: 16 } })
894
+ },
895
+ {
896
+ type: "wait",
897
+ label: "Wait",
898
+ icon: /* @__PURE__ */ jsx(Clock, { style: { width: 16, height: 16 } })
899
+ },
900
+ {
901
+ type: "branch",
902
+ label: "Branch",
903
+ icon: /* @__PURE__ */ jsx(ArrowRight, { style: { width: 16, height: 16 } })
904
+ },
905
+ {
906
+ type: "exit",
907
+ label: "Exit",
908
+ icon: /* @__PURE__ */ jsx(Cross, { style: { width: 16, height: 16 } })
909
+ }
910
+ ];
911
+ const FlowToolbar = ({ onAddNode, onAutoLayout, disabled }) => {
912
+ return /* @__PURE__ */ jsx(
913
+ Box,
914
+ {
915
+ padding: 2,
916
+ background: "neutral0",
917
+ hasRadius: true,
918
+ shadow: "filterShadow",
919
+ style: {
920
+ position: "absolute",
921
+ top: 10,
922
+ left: 10,
923
+ zIndex: 5
924
+ },
925
+ children: /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", children: [
926
+ /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", style: { paddingRight: 8 }, children: "Add Step:" }),
927
+ TOOLBAR_ITEMS.map((item) => {
928
+ const colors = NODE_COLORS[item.type];
929
+ return /* @__PURE__ */ jsx(Tooltip, { label: `Add ${item.label} step`, children: /* @__PURE__ */ jsx(
930
+ Button,
931
+ {
932
+ variant: "tertiary",
933
+ size: "S",
934
+ startIcon: item.icon,
935
+ onClick: () => onAddNode(item.type),
936
+ disabled,
937
+ style: {
938
+ backgroundColor: colors.background,
939
+ color: colors.text,
940
+ borderColor: colors.border
941
+ },
942
+ children: item.label
943
+ }
944
+ ) }, item.type);
945
+ }),
946
+ /* @__PURE__ */ jsx(Box, { style: { borderLeft: "1px solid #dcdce4", height: 24, marginLeft: 8 } }),
947
+ /* @__PURE__ */ jsx(Tooltip, { label: "Auto-arrange nodes", children: /* @__PURE__ */ jsx(
948
+ Button,
949
+ {
950
+ variant: "secondary",
951
+ size: "S",
952
+ startIcon: /* @__PURE__ */ jsx(Layout, { style: { width: 16, height: 16 } }),
953
+ onClick: onAutoLayout,
954
+ disabled,
955
+ children: "Auto Layout"
956
+ }
957
+ ) })
958
+ ] })
959
+ }
960
+ );
961
+ };
962
+ const generateStepKey = () => {
963
+ return `step_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
964
+ };
965
+ const parseValue = (value) => {
966
+ if (!value) return [];
967
+ if (Array.isArray(value)) return value;
968
+ if (typeof value === "string") {
969
+ try {
970
+ const parsed = JSON.parse(value);
971
+ return Array.isArray(parsed) ? parsed : [];
972
+ } catch {
973
+ return [];
974
+ }
975
+ }
976
+ return [];
977
+ };
978
+ const stepsToFlow = (steps) => {
979
+ const nodes = [];
980
+ const edges = [];
981
+ const entryNode = {
982
+ id: ENTRY_NODE_ID,
983
+ type: "entry",
984
+ position: { x: 250, y: 0 },
985
+ data: {
986
+ stepType: "entry",
987
+ name: "Entry",
988
+ config: {}
989
+ },
990
+ deletable: false
991
+ };
992
+ nodes.push(entryNode);
993
+ const sortedSteps = [...steps].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
994
+ sortedSteps.forEach((step, index) => {
995
+ const node = {
996
+ id: step.stepKey,
997
+ type: step.stepType,
998
+ position: { x: 250, y: (index + 1) * 150 },
999
+ data: {
1000
+ stepType: step.stepType,
1001
+ name: step.name,
1002
+ config: {
1003
+ channel: step.channel,
1004
+ variants: step.variants,
1005
+ duration: step.duration,
1006
+ durationUnit: step.durationUnit,
1007
+ branchSegment: step.branchSegment
1008
+ }
1009
+ }
1010
+ };
1011
+ nodes.push(node);
1012
+ });
1013
+ if (sortedSteps.length > 0) {
1014
+ edges.push({
1015
+ id: `${ENTRY_NODE_ID}-${sortedSteps[0].stepKey}`,
1016
+ source: ENTRY_NODE_ID,
1017
+ target: sortedSteps[0].stepKey
1018
+ });
1019
+ }
1020
+ sortedSteps.forEach((step) => {
1021
+ if (step.stepType === "branch") {
1022
+ if (step.yesNextStep) {
1023
+ edges.push({
1024
+ id: `${step.stepKey}-${step.yesNextStep}-yes`,
1025
+ source: step.stepKey,
1026
+ sourceHandle: "yes",
1027
+ target: step.yesNextStep,
1028
+ type: "labeled",
1029
+ data: { label: "Yes" }
1030
+ });
1031
+ }
1032
+ if (step.noNextStep) {
1033
+ edges.push({
1034
+ id: `${step.stepKey}-${step.noNextStep}-no`,
1035
+ source: step.stepKey,
1036
+ sourceHandle: "no",
1037
+ target: step.noNextStep,
1038
+ type: "labeled",
1039
+ data: { label: "No" }
1040
+ });
1041
+ }
1042
+ } else if (step.nextStep) {
1043
+ edges.push({
1044
+ id: `${step.stepKey}-${step.nextStep}`,
1045
+ source: step.stepKey,
1046
+ target: step.nextStep
1047
+ });
1048
+ }
1049
+ });
1050
+ return { nodes, edges };
1051
+ };
1052
+ const flowToSteps = (nodes, edges) => {
1053
+ const stepNodes = nodes.filter((n) => n.data.stepType !== "entry");
1054
+ const nodePositions = new Map(nodes.map((n) => [n.id, n.position.y]));
1055
+ const sorted = [...stepNodes].sort(
1056
+ (a, b) => (nodePositions.get(a.id) ?? 0) - (nodePositions.get(b.id) ?? 0)
1057
+ );
1058
+ return sorted.map((node, index) => {
1059
+ const step = {
1060
+ stepKey: node.id,
1061
+ name: node.data.name,
1062
+ stepType: node.data.stepType,
1063
+ order: index,
1064
+ ...node.data.config
1065
+ };
1066
+ const outEdges = edges.filter((e) => e.source === node.id);
1067
+ if (node.data.stepType === "branch") {
1068
+ const yesEdge = outEdges.find((e) => e.sourceHandle === "yes");
1069
+ const noEdge = outEdges.find((e) => e.sourceHandle === "no");
1070
+ step.yesNextStep = yesEdge?.target;
1071
+ step.noNextStep = noEdge?.target;
1072
+ } else {
1073
+ step.nextStep = outEdges[0]?.target;
1074
+ }
1075
+ return step;
1076
+ });
1077
+ };
1078
+ const applyAutoLayout = (nodes, edges) => {
1079
+ const g = new dagre.graphlib.Graph();
1080
+ g.setGraph({ rankdir: "TB", nodesep: 80, ranksep: 120, marginx: 20, marginy: 20 });
1081
+ g.setDefaultEdgeLabel(() => ({}));
1082
+ nodes.forEach((node) => {
1083
+ g.setNode(node.id, { width: NODE_DIMENSIONS.width, height: NODE_DIMENSIONS.height });
1084
+ });
1085
+ edges.forEach((edge) => {
1086
+ g.setEdge(edge.source, edge.target);
1087
+ });
1088
+ dagre.layout(g);
1089
+ return nodes.map((node) => {
1090
+ const nodeWithPosition = g.node(node.id);
1091
+ return {
1092
+ ...node,
1093
+ position: {
1094
+ x: nodeWithPosition.x - NODE_DIMENSIONS.width / 2,
1095
+ y: nodeWithPosition.y - NODE_DIMENSIONS.height / 2
1096
+ }
1097
+ };
1098
+ });
1099
+ };
1100
+ const createNode = (type, position) => {
1101
+ const id = type === "entry" ? ENTRY_NODE_ID : generateStepKey();
1102
+ const defaults = DEFAULT_NODE_DATA[type];
1103
+ return {
1104
+ id,
1105
+ type,
1106
+ position,
1107
+ data: {
1108
+ stepType: type,
1109
+ name: defaults.name,
1110
+ config: { ...defaults.config }
1111
+ },
1112
+ deletable: type !== "entry"
1113
+ };
1114
+ };
1115
+ const getNextNodePosition = (nodes) => {
1116
+ if (nodes.length === 0) {
1117
+ return { x: 250, y: 0 };
1118
+ }
1119
+ const maxY = Math.max(...nodes.map((n) => n.position.y));
1120
+ return { x: 250, y: maxY + 150 };
1121
+ };
1122
+ const isValidConnection = (source, sourceHandle, target, nodes, edges) => {
1123
+ if (source === target) return false;
1124
+ const targetNode = nodes.find((n) => n.id === target);
1125
+ if (!targetNode) return false;
1126
+ if (targetNode.data.stepType === "entry") return false;
1127
+ const sourceNode = nodes.find((n) => n.id === source);
1128
+ if (!sourceNode) return false;
1129
+ if (sourceNode.data.stepType === "exit") return false;
1130
+ const existingEdge = edges.find(
1131
+ (e) => e.source === source && (sourceHandle ? e.sourceHandle === sourceHandle : !e.sourceHandle)
1132
+ );
1133
+ if (existingEdge) return false;
1134
+ return true;
1135
+ };
1136
+ const StepFlowBuilderInner = forwardRef(
1137
+ ({ name, value, onChange, intlLabel, disabled, error, required, hint }, ref) => {
1138
+ const [nodes, setNodes, onNodesChange] = useNodesState([]);
1139
+ const [edges, setEdges, onEdgesChange] = useEdgesState([]);
1140
+ const [selectedNode, setSelectedNode] = useState(null);
1141
+ const isInitialMount = useRef(true);
1142
+ const syncTimeoutRef = useRef(null);
1143
+ useEffect(() => {
1144
+ const steps = parseValue(value);
1145
+ const { nodes: n, edges: e } = stepsToFlow(steps);
1146
+ if (n.length === 0) {
1147
+ const entryNode = createNode("entry", { x: 250, y: 0 });
1148
+ setNodes([entryNode]);
1149
+ setEdges([]);
1150
+ } else {
1151
+ const layoutedNodes = applyAutoLayout(n, e);
1152
+ setNodes(layoutedNodes);
1153
+ setEdges(e);
1154
+ }
1155
+ isInitialMount.current = false;
1156
+ }, []);
1157
+ const syncToStrapi = useCallback(() => {
1158
+ if (isInitialMount.current) return;
1159
+ if (syncTimeoutRef.current) {
1160
+ clearTimeout(syncTimeoutRef.current);
1161
+ }
1162
+ syncTimeoutRef.current = setTimeout(() => {
1163
+ const steps = flowToSteps(nodes, edges);
1164
+ const serialized = JSON.stringify(steps);
1165
+ onChange({ target: { name, value: serialized } });
1166
+ }, 300);
1167
+ }, [nodes, edges, name, onChange]);
1168
+ useEffect(() => {
1169
+ syncToStrapi();
1170
+ }, [nodes, edges, syncToStrapi]);
1171
+ useEffect(() => {
1172
+ return () => {
1173
+ if (syncTimeoutRef.current) {
1174
+ clearTimeout(syncTimeoutRef.current);
1175
+ }
1176
+ };
1177
+ }, []);
1178
+ const handleNodesChange = useCallback(
1179
+ (changes) => {
1180
+ const filteredChanges = changes.filter((change) => {
1181
+ if (change.type === "remove" && change.id === ENTRY_NODE_ID) {
1182
+ return false;
1183
+ }
1184
+ return true;
1185
+ });
1186
+ onNodesChange(filteredChanges);
1187
+ },
1188
+ [onNodesChange]
1189
+ );
1190
+ const handleEdgesChange = useCallback(
1191
+ (changes) => {
1192
+ onEdgesChange(changes);
1193
+ },
1194
+ [onEdgesChange]
1195
+ );
1196
+ const handleConnect = useCallback(
1197
+ (connection) => {
1198
+ if (!connection.source || !connection.target) return;
1199
+ if (!isValidConnection(
1200
+ connection.source,
1201
+ connection.sourceHandle || null,
1202
+ connection.target,
1203
+ nodes,
1204
+ edges
1205
+ )) {
1206
+ return;
1207
+ }
1208
+ const sourceNode = nodes.find((n) => n.id === connection.source);
1209
+ const isBranch = sourceNode?.data.stepType === "branch";
1210
+ const newEdge = {
1211
+ id: `${connection.source}-${connection.target}${connection.sourceHandle ? `-${connection.sourceHandle}` : ""}`,
1212
+ source: connection.source,
1213
+ target: connection.target,
1214
+ sourceHandle: connection.sourceHandle || void 0,
1215
+ targetHandle: connection.targetHandle || void 0,
1216
+ type: isBranch ? "labeled" : "default"
1217
+ };
1218
+ setEdges((eds) => addEdge(newEdge, eds));
1219
+ },
1220
+ [nodes, edges, setEdges]
1221
+ );
1222
+ const handleNodeClick = useCallback(
1223
+ (_, node) => {
1224
+ setSelectedNode(node);
1225
+ },
1226
+ []
1227
+ );
1228
+ const handlePaneClick = useCallback(() => {
1229
+ setSelectedNode(null);
1230
+ }, []);
1231
+ const handleAddNode = useCallback(
1232
+ (type) => {
1233
+ const position = getNextNodePosition(nodes);
1234
+ const newNode = createNode(type, position);
1235
+ setNodes((nds) => [...nds, newNode]);
1236
+ },
1237
+ [nodes, setNodes]
1238
+ );
1239
+ const handleAutoLayout = useCallback(() => {
1240
+ const layoutedNodes = applyAutoLayout(nodes, edges);
1241
+ setNodes(layoutedNodes);
1242
+ }, [nodes, edges, setNodes]);
1243
+ const handleNodeUpdate = useCallback(
1244
+ (nodeId, data) => {
1245
+ setNodes(
1246
+ (nds) => nds.map((node) => {
1247
+ if (node.id === nodeId) {
1248
+ return {
1249
+ ...node,
1250
+ data: {
1251
+ ...node.data,
1252
+ ...data,
1253
+ config: {
1254
+ ...node.data.config,
1255
+ ...data.config || {}
1256
+ }
1257
+ }
1258
+ };
1259
+ }
1260
+ return node;
1261
+ })
1262
+ );
1263
+ if (selectedNode && selectedNode.id === nodeId) {
1264
+ setSelectedNode(
1265
+ (prev) => prev ? {
1266
+ ...prev,
1267
+ data: {
1268
+ ...prev.data,
1269
+ ...data,
1270
+ config: {
1271
+ ...prev.data.config,
1272
+ ...data.config || {}
1273
+ }
1274
+ }
1275
+ } : null
1276
+ );
1277
+ }
1278
+ },
1279
+ [selectedNode, setNodes]
1280
+ );
1281
+ const handleClosePanel = useCallback(() => {
1282
+ setSelectedNode(null);
1283
+ }, []);
1284
+ const displayLabel = useMemo(() => {
1285
+ if (intlLabel?.defaultMessage && !intlLabel.defaultMessage.includes(".")) {
1286
+ return intlLabel.defaultMessage;
1287
+ }
1288
+ return "Campaign Steps";
1289
+ }, [intlLabel]);
1290
+ const stepCount = nodes.filter((n) => n.data.stepType !== "entry").length;
1291
+ return /* @__PURE__ */ jsxs(Field.Root, { name, error, hint, required, children: [
1292
+ /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", children: [
1293
+ /* @__PURE__ */ jsx(Field.Label, { children: displayLabel }),
1294
+ /* @__PURE__ */ jsxs(Badge, { backgroundColor: "neutral100", textColor: "neutral600", children: [
1295
+ stepCount,
1296
+ " step",
1297
+ stepCount !== 1 ? "s" : ""
1298
+ ] })
1299
+ ] }),
1300
+ /* @__PURE__ */ jsx(
1301
+ Box,
1302
+ {
1303
+ paddingTop: 2,
1304
+ ref,
1305
+ style: { position: "relative", height: 600 },
1306
+ children: /* @__PURE__ */ jsxs(
1307
+ Box,
1308
+ {
1309
+ style: {
1310
+ height: "100%",
1311
+ border: error ? "1px solid #ee5e52" : "1px solid #dcdce4",
1312
+ borderRadius: 4,
1313
+ overflow: "hidden"
1314
+ },
1315
+ children: [
1316
+ /* @__PURE__ */ jsxs(
1317
+ ReactFlow,
1318
+ {
1319
+ nodes,
1320
+ edges,
1321
+ nodeTypes,
1322
+ edgeTypes,
1323
+ onNodesChange: handleNodesChange,
1324
+ onEdgesChange: handleEdgesChange,
1325
+ onConnect: handleConnect,
1326
+ onNodeClick: handleNodeClick,
1327
+ onPaneClick: handlePaneClick,
1328
+ fitView: true,
1329
+ fitViewOptions: { padding: 0.2 },
1330
+ deleteKeyCode: ["Backspace", "Delete"],
1331
+ nodesDraggable: !disabled,
1332
+ nodesConnectable: !disabled,
1333
+ elementsSelectable: !disabled,
1334
+ panOnScroll: true,
1335
+ selectionOnDrag: true,
1336
+ defaultEdgeOptions: {
1337
+ type: "smoothstep",
1338
+ animated: false
1339
+ },
1340
+ children: [
1341
+ /* @__PURE__ */ jsx(Background, { color: "#f0f0f0", gap: 20 }),
1342
+ /* @__PURE__ */ jsx(Controls, { showInteractive: false }),
1343
+ /* @__PURE__ */ jsx(
1344
+ MiniMap,
1345
+ {
1346
+ nodeColor: (node) => {
1347
+ switch (node.data?.stepType) {
1348
+ case "entry":
1349
+ return "#5cb176";
1350
+ case "message":
1351
+ return "#0077cc";
1352
+ case "wait":
1353
+ return "#e9b200";
1354
+ case "branch":
1355
+ return "#7b61ff";
1356
+ case "exit":
1357
+ return "#dc2626";
1358
+ default:
1359
+ return "#999";
1360
+ }
1361
+ },
1362
+ maskColor: "rgba(0, 0, 0, 0.1)",
1363
+ style: { background: "#f7f7f7" }
1364
+ }
1365
+ ),
1366
+ /* @__PURE__ */ jsx(
1367
+ FlowToolbar,
1368
+ {
1369
+ onAddNode: handleAddNode,
1370
+ onAutoLayout: handleAutoLayout,
1371
+ disabled
1372
+ }
1373
+ )
1374
+ ]
1375
+ }
1376
+ ),
1377
+ /* @__PURE__ */ jsx(
1378
+ NodeEditPanel,
1379
+ {
1380
+ node: selectedNode,
1381
+ onClose: handleClosePanel,
1382
+ onUpdate: handleNodeUpdate,
1383
+ disabled
1384
+ }
1385
+ )
1386
+ ]
1387
+ }
1388
+ )
1389
+ }
1390
+ ),
1391
+ /* @__PURE__ */ jsx(Field.Hint, {}),
1392
+ /* @__PURE__ */ jsx(Field.Error, {})
1393
+ ] });
1394
+ }
1395
+ );
1396
+ StepFlowBuilderInner.displayName = "StepFlowBuilderInner";
1397
+ const StepFlowBuilder = forwardRef(
1398
+ (props, ref) => {
1399
+ return /* @__PURE__ */ jsx(ReactFlowProvider, { children: /* @__PURE__ */ jsx(StepFlowBuilderInner, { ...props, ref }) });
1400
+ }
1401
+ );
1402
+ StepFlowBuilder.displayName = "StepFlowBuilder";
1403
+ export {
1404
+ StepFlowBuilder as default
1405
+ };