@inspirer-dev/crm-dashboard 1.0.21 → 1.0.22

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-T7-DTUJN.js} +262 -227
  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,91 @@
1
+ import type { StepType, ChannelType, DurationUnit } from './types';
2
+
3
+ export const NODE_COLORS: Record<
4
+ StepType,
5
+ { background: string; border: string; text: string }
6
+ > = {
7
+ entry: {
8
+ background: '#eef5eb',
9
+ border: '#5cb176',
10
+ text: '#297c3b',
11
+ },
12
+ message: {
13
+ background: '#eef8ff',
14
+ border: '#0077cc',
15
+ text: '#0055a4',
16
+ },
17
+ wait: {
18
+ background: '#fffae6',
19
+ border: '#e9b200',
20
+ text: '#a16207',
21
+ },
22
+ branch: {
23
+ background: '#f5f0ff',
24
+ border: '#7b61ff',
25
+ text: '#5746af',
26
+ },
27
+ exit: {
28
+ background: '#fef1f0',
29
+ border: '#dc2626',
30
+ text: '#c03030',
31
+ },
32
+ };
33
+
34
+ export const NODE_DIMENSIONS = {
35
+ width: 220,
36
+ height: 80,
37
+ };
38
+
39
+ export const STEP_TYPE_LABELS: Record<StepType, string> = {
40
+ entry: 'Entry Point',
41
+ message: 'Send Message',
42
+ wait: 'Wait',
43
+ branch: 'Branch',
44
+ exit: 'Exit',
45
+ };
46
+
47
+ export const CHANNEL_OPTIONS: { value: ChannelType; label: string }[] = [
48
+ { value: 'telegram', label: 'Telegram' },
49
+ { value: 'email', label: 'Email' },
50
+ { value: 'push', label: 'Push Notification' },
51
+ { value: 'sms', label: 'SMS' },
52
+ ];
53
+
54
+ export const DURATION_UNIT_OPTIONS: { value: DurationUnit; label: string }[] = [
55
+ { value: 'minutes', label: 'Minutes' },
56
+ { value: 'hours', label: 'Hours' },
57
+ { value: 'days', label: 'Days' },
58
+ ];
59
+
60
+ export const DEFAULT_NODE_DATA: Record<StepType, { name: string; config: object }> = {
61
+ entry: {
62
+ name: 'Entry',
63
+ config: {},
64
+ },
65
+ message: {
66
+ name: 'Send Message',
67
+ config: {
68
+ channel: 'telegram' as ChannelType,
69
+ variants: [],
70
+ },
71
+ },
72
+ wait: {
73
+ name: 'Wait',
74
+ config: {
75
+ duration: 1,
76
+ durationUnit: 'hours' as DurationUnit,
77
+ },
78
+ },
79
+ branch: {
80
+ name: 'Branch',
81
+ config: {
82
+ branchSegment: null,
83
+ },
84
+ },
85
+ exit: {
86
+ name: 'Exit',
87
+ config: {},
88
+ },
89
+ };
90
+
91
+ export const ENTRY_NODE_ID = 'entry';
@@ -0,0 +1,77 @@
1
+ import React from 'react';
2
+ import {
3
+ BaseEdge,
4
+ EdgeLabelRenderer,
5
+ getBezierPath,
6
+ type EdgeProps,
7
+ } from 'reactflow';
8
+
9
+ interface LabeledEdgeData {
10
+ label?: string;
11
+ }
12
+
13
+ const LabeledEdge: React.FC<EdgeProps<LabeledEdgeData>> = ({
14
+ id,
15
+ sourceX,
16
+ sourceY,
17
+ targetX,
18
+ targetY,
19
+ sourcePosition,
20
+ targetPosition,
21
+ sourceHandle,
22
+ style = {},
23
+ markerEnd,
24
+ }) => {
25
+ const [edgePath, labelX, labelY] = getBezierPath({
26
+ sourceX,
27
+ sourceY,
28
+ sourcePosition,
29
+ targetX,
30
+ targetY,
31
+ targetPosition,
32
+ });
33
+
34
+ const isYes = sourceHandle === 'yes';
35
+ const isNo = sourceHandle === 'no';
36
+ const label = isYes ? 'Yes' : isNo ? 'No' : null;
37
+ const bgColor = isYes ? '#dcfce7' : isNo ? '#fef2f2' : '#fff';
38
+ const textColor = isYes ? '#15803d' : isNo ? '#dc2626' : '#333';
39
+ const borderColor = isYes ? '#86efac' : isNo ? '#fca5a5' : '#ddd';
40
+
41
+ return (
42
+ <>
43
+ <BaseEdge
44
+ path={edgePath}
45
+ markerEnd={markerEnd}
46
+ style={{
47
+ ...style,
48
+ stroke: isYes ? '#5cb176' : isNo ? '#dc2626' : '#b1b1b7',
49
+ strokeWidth: 2,
50
+ }}
51
+ />
52
+ {label && (
53
+ <EdgeLabelRenderer>
54
+ <div
55
+ style={{
56
+ position: 'absolute',
57
+ transform: `translate(-50%, -50%) translate(${labelX}px, ${labelY}px)`,
58
+ fontSize: 11,
59
+ fontWeight: 600,
60
+ background: bgColor,
61
+ color: textColor,
62
+ padding: '2px 8px',
63
+ borderRadius: 4,
64
+ border: `1px solid ${borderColor}`,
65
+ pointerEvents: 'all',
66
+ }}
67
+ className="nodrag nopan"
68
+ >
69
+ {label}
70
+ </div>
71
+ </EdgeLabelRenderer>
72
+ )}
73
+ </>
74
+ );
75
+ };
76
+
77
+ export default LabeledEdge;
@@ -0,0 +1,8 @@
1
+ import type { EdgeTypes } from 'reactflow';
2
+ import LabeledEdge from './LabeledEdge';
3
+
4
+ export { LabeledEdge };
5
+
6
+ export const edgeTypes: EdgeTypes = {
7
+ labeled: LabeledEdge,
8
+ };
@@ -0,0 +1,320 @@
1
+ import React, { forwardRef, useCallback, useEffect, useMemo, useState, useRef } from 'react';
2
+ import ReactFlow, {
3
+ Background,
4
+ Controls,
5
+ MiniMap,
6
+ useNodesState,
7
+ useEdgesState,
8
+ addEdge,
9
+ type Connection,
10
+ type OnNodesChange,
11
+ type OnEdgesChange,
12
+ type OnConnect,
13
+ ReactFlowProvider,
14
+ } from 'reactflow';
15
+ import 'reactflow/dist/style.css';
16
+ import { Box, Field, Flex, Typography, Badge } from '@strapi/design-system';
17
+
18
+ import type { StepFlowBuilderProps, FlowNode, FlowEdge, FlowNodeData, StepType } from './types';
19
+ import { nodeTypes } from './nodes';
20
+ import { edgeTypes } from './edges';
21
+ import { NodeEditPanel } from './panels';
22
+ import { FlowToolbar } from './toolbar';
23
+ import {
24
+ parseValue,
25
+ stepsToFlow,
26
+ flowToSteps,
27
+ applyAutoLayout,
28
+ createNode,
29
+ getNextNodePosition,
30
+ isValidConnection,
31
+ } from './utils';
32
+ import { ENTRY_NODE_ID } from './constants';
33
+
34
+ const StepFlowBuilderInner = forwardRef<HTMLDivElement, StepFlowBuilderProps>(
35
+ ({ name, value, onChange, intlLabel, disabled, error, required, hint }, ref) => {
36
+ const [nodes, setNodes, onNodesChange] = useNodesState<FlowNodeData>([]);
37
+ const [edges, setEdges, onEdgesChange] = useEdgesState([]);
38
+ const [selectedNode, setSelectedNode] = useState<FlowNode | null>(null);
39
+ const isInitialMount = useRef(true);
40
+ const syncTimeoutRef = useRef<NodeJS.Timeout | null>(null);
41
+
42
+ useEffect(() => {
43
+ const steps = parseValue(value);
44
+ const { nodes: n, edges: e } = stepsToFlow(steps);
45
+
46
+ if (n.length === 0) {
47
+ const entryNode = createNode('entry', { x: 250, y: 0 });
48
+ setNodes([entryNode]);
49
+ setEdges([]);
50
+ } else {
51
+ const layoutedNodes = applyAutoLayout(n, e);
52
+ setNodes(layoutedNodes);
53
+ setEdges(e);
54
+ }
55
+ isInitialMount.current = false;
56
+ }, []);
57
+
58
+ const syncToStrapi = useCallback(() => {
59
+ if (isInitialMount.current) return;
60
+
61
+ if (syncTimeoutRef.current) {
62
+ clearTimeout(syncTimeoutRef.current);
63
+ }
64
+
65
+ syncTimeoutRef.current = setTimeout(() => {
66
+ const steps = flowToSteps(nodes, edges);
67
+ const serialized = JSON.stringify(steps);
68
+ onChange({ target: { name, value: serialized } });
69
+ }, 300);
70
+ }, [nodes, edges, name, onChange]);
71
+
72
+ useEffect(() => {
73
+ syncToStrapi();
74
+ }, [nodes, edges, syncToStrapi]);
75
+
76
+ useEffect(() => {
77
+ return () => {
78
+ if (syncTimeoutRef.current) {
79
+ clearTimeout(syncTimeoutRef.current);
80
+ }
81
+ };
82
+ }, []);
83
+
84
+ const handleNodesChange: OnNodesChange = useCallback(
85
+ (changes) => {
86
+ const filteredChanges = changes.filter((change) => {
87
+ if (change.type === 'remove' && change.id === ENTRY_NODE_ID) {
88
+ return false;
89
+ }
90
+ return true;
91
+ });
92
+ onNodesChange(filteredChanges);
93
+ },
94
+ [onNodesChange]
95
+ );
96
+
97
+ const handleEdgesChange: OnEdgesChange = useCallback(
98
+ (changes) => {
99
+ onEdgesChange(changes);
100
+ },
101
+ [onEdgesChange]
102
+ );
103
+
104
+ const handleConnect: OnConnect = useCallback(
105
+ (connection: Connection) => {
106
+ if (!connection.source || !connection.target) return;
107
+
108
+ if (
109
+ !isValidConnection(
110
+ connection.source,
111
+ connection.sourceHandle || null,
112
+ connection.target,
113
+ nodes,
114
+ edges
115
+ )
116
+ ) {
117
+ return;
118
+ }
119
+
120
+ const sourceNode = nodes.find((n) => n.id === connection.source);
121
+ const isBranch = sourceNode?.data.stepType === 'branch';
122
+
123
+ const newEdge: FlowEdge = {
124
+ id: `${connection.source}-${connection.target}${
125
+ connection.sourceHandle ? `-${connection.sourceHandle}` : ''
126
+ }`,
127
+ source: connection.source,
128
+ target: connection.target,
129
+ sourceHandle: connection.sourceHandle || undefined,
130
+ targetHandle: connection.targetHandle || undefined,
131
+ type: isBranch ? 'labeled' : 'default',
132
+ };
133
+
134
+ setEdges((eds) => addEdge(newEdge, eds));
135
+ },
136
+ [nodes, edges, setEdges]
137
+ );
138
+
139
+ const handleNodeClick = useCallback(
140
+ (_: React.MouseEvent, node: FlowNode) => {
141
+ setSelectedNode(node);
142
+ },
143
+ []
144
+ );
145
+
146
+ const handlePaneClick = useCallback(() => {
147
+ setSelectedNode(null);
148
+ }, []);
149
+
150
+ const handleAddNode = useCallback(
151
+ (type: StepType) => {
152
+ const position = getNextNodePosition(nodes);
153
+ const newNode = createNode(type, position);
154
+ setNodes((nds) => [...nds, newNode]);
155
+ },
156
+ [nodes, setNodes]
157
+ );
158
+
159
+ const handleAutoLayout = useCallback(() => {
160
+ const layoutedNodes = applyAutoLayout(nodes, edges);
161
+ setNodes(layoutedNodes);
162
+ }, [nodes, edges, setNodes]);
163
+
164
+ const handleNodeUpdate = useCallback(
165
+ (nodeId: string, data: Partial<FlowNodeData>) => {
166
+ setNodes((nds) =>
167
+ nds.map((node) => {
168
+ if (node.id === nodeId) {
169
+ return {
170
+ ...node,
171
+ data: {
172
+ ...node.data,
173
+ ...data,
174
+ config: {
175
+ ...node.data.config,
176
+ ...(data.config || {}),
177
+ },
178
+ },
179
+ };
180
+ }
181
+ return node;
182
+ })
183
+ );
184
+
185
+ if (selectedNode && selectedNode.id === nodeId) {
186
+ setSelectedNode((prev) =>
187
+ prev
188
+ ? {
189
+ ...prev,
190
+ data: {
191
+ ...prev.data,
192
+ ...data,
193
+ config: {
194
+ ...prev.data.config,
195
+ ...(data.config || {}),
196
+ },
197
+ },
198
+ }
199
+ : null
200
+ );
201
+ }
202
+ },
203
+ [selectedNode, setNodes]
204
+ );
205
+
206
+ const handleClosePanel = useCallback(() => {
207
+ setSelectedNode(null);
208
+ }, []);
209
+
210
+ const displayLabel = useMemo(() => {
211
+ if (intlLabel?.defaultMessage && !intlLabel.defaultMessage.includes('.')) {
212
+ return intlLabel.defaultMessage;
213
+ }
214
+ return 'Campaign Steps';
215
+ }, [intlLabel]);
216
+
217
+ const stepCount = nodes.filter((n) => n.data.stepType !== 'entry').length;
218
+
219
+ return (
220
+ <Field.Root name={name} error={error} hint={hint} required={required}>
221
+ <Flex justifyContent="space-between" alignItems="center">
222
+ <Field.Label>{displayLabel}</Field.Label>
223
+ <Badge backgroundColor="neutral100" textColor="neutral600">
224
+ {stepCount} step{stepCount !== 1 ? 's' : ''}
225
+ </Badge>
226
+ </Flex>
227
+ <Box
228
+ paddingTop={2}
229
+ ref={ref}
230
+ style={{ position: 'relative', height: 600 }}
231
+ >
232
+ <Box
233
+ style={{
234
+ height: '100%',
235
+ border: error ? '1px solid #ee5e52' : '1px solid #dcdce4',
236
+ borderRadius: 4,
237
+ overflow: 'hidden',
238
+ }}
239
+ >
240
+ <ReactFlow
241
+ nodes={nodes}
242
+ edges={edges}
243
+ nodeTypes={nodeTypes}
244
+ edgeTypes={edgeTypes}
245
+ onNodesChange={handleNodesChange}
246
+ onEdgesChange={handleEdgesChange}
247
+ onConnect={handleConnect}
248
+ onNodeClick={handleNodeClick}
249
+ onPaneClick={handlePaneClick}
250
+ fitView
251
+ fitViewOptions={{ padding: 0.2 }}
252
+ deleteKeyCode={['Backspace', 'Delete']}
253
+ nodesDraggable={!disabled}
254
+ nodesConnectable={!disabled}
255
+ elementsSelectable={!disabled}
256
+ panOnScroll
257
+ selectionOnDrag
258
+ defaultEdgeOptions={{
259
+ type: 'smoothstep',
260
+ animated: false,
261
+ }}
262
+ >
263
+ <Background color="#f0f0f0" gap={20} />
264
+ <Controls showInteractive={false} />
265
+ <MiniMap
266
+ nodeColor={(node) => {
267
+ switch (node.data?.stepType) {
268
+ case 'entry':
269
+ return '#5cb176';
270
+ case 'message':
271
+ return '#0077cc';
272
+ case 'wait':
273
+ return '#e9b200';
274
+ case 'branch':
275
+ return '#7b61ff';
276
+ case 'exit':
277
+ return '#dc2626';
278
+ default:
279
+ return '#999';
280
+ }
281
+ }}
282
+ maskColor="rgba(0, 0, 0, 0.1)"
283
+ style={{ background: '#f7f7f7' }}
284
+ />
285
+ <FlowToolbar
286
+ onAddNode={handleAddNode}
287
+ onAutoLayout={handleAutoLayout}
288
+ disabled={disabled}
289
+ />
290
+ </ReactFlow>
291
+ <NodeEditPanel
292
+ node={selectedNode}
293
+ onClose={handleClosePanel}
294
+ onUpdate={handleNodeUpdate}
295
+ disabled={disabled}
296
+ />
297
+ </Box>
298
+ </Box>
299
+ <Field.Hint />
300
+ <Field.Error />
301
+ </Field.Root>
302
+ );
303
+ }
304
+ );
305
+
306
+ StepFlowBuilderInner.displayName = 'StepFlowBuilderInner';
307
+
308
+ const StepFlowBuilder = forwardRef<HTMLDivElement, StepFlowBuilderProps>(
309
+ (props, ref) => {
310
+ return (
311
+ <ReactFlowProvider>
312
+ <StepFlowBuilderInner {...props} ref={ref} />
313
+ </ReactFlowProvider>
314
+ );
315
+ }
316
+ );
317
+
318
+ StepFlowBuilder.displayName = 'StepFlowBuilder';
319
+
320
+ export default StepFlowBuilder;
@@ -0,0 +1,90 @@
1
+ import React, { memo } from 'react';
2
+ import { Handle, Position, type NodeProps } from 'reactflow';
3
+ import { Flex, Typography, Badge } from '@strapi/design-system';
4
+ import { ArrowRight } from '@strapi/icons';
5
+ import type { FlowNodeData } from '../types';
6
+ import { NODE_COLORS, NODE_DIMENSIONS } from '../constants';
7
+
8
+ const BranchNode: React.FC<NodeProps<FlowNodeData>> = ({ data, selected }) => {
9
+ const colors = NODE_COLORS.branch;
10
+ const segmentName = data.config.branchSegment?.name || 'No segment';
11
+
12
+ return (
13
+ <>
14
+ <Handle
15
+ type="target"
16
+ position={Position.Top}
17
+ style={{
18
+ background: colors.border,
19
+ width: 10,
20
+ height: 10,
21
+ border: '2px solid white',
22
+ }}
23
+ />
24
+ <Flex
25
+ direction="column"
26
+ alignItems="center"
27
+ justifyContent="center"
28
+ gap={1}
29
+ padding={3}
30
+ background="neutral0"
31
+ hasRadius
32
+ style={{
33
+ width: NODE_DIMENSIONS.width,
34
+ height: NODE_DIMENSIONS.height,
35
+ border: `2px solid ${selected ? colors.border : colors.background}`,
36
+ backgroundColor: colors.background,
37
+ boxShadow: selected ? `0 0 0 2px ${colors.border}` : 'none',
38
+ }}
39
+ >
40
+ <Flex alignItems="center" gap={2}>
41
+ <ArrowRight style={{ color: colors.text, width: 18, height: 18 }} />
42
+ <Typography
43
+ variant="omega"
44
+ fontWeight="semiBold"
45
+ style={{ color: colors.text }}
46
+ ellipsis
47
+ >
48
+ {data.name}
49
+ </Typography>
50
+ </Flex>
51
+ <Badge
52
+ size="S"
53
+ backgroundColor="secondary100"
54
+ textColor="secondary700"
55
+ style={{ maxWidth: '90%' }}
56
+ >
57
+ <Typography variant="pi" ellipsis>
58
+ {segmentName}
59
+ </Typography>
60
+ </Badge>
61
+ </Flex>
62
+ <Handle
63
+ type="source"
64
+ position={Position.Bottom}
65
+ id="yes"
66
+ style={{
67
+ background: '#5cb176',
68
+ width: 10,
69
+ height: 10,
70
+ border: '2px solid white',
71
+ left: '30%',
72
+ }}
73
+ />
74
+ <Handle
75
+ type="source"
76
+ position={Position.Bottom}
77
+ id="no"
78
+ style={{
79
+ background: '#dc2626',
80
+ width: 10,
81
+ height: 10,
82
+ border: '2px solid white',
83
+ left: '70%',
84
+ }}
85
+ />
86
+ </>
87
+ );
88
+ };
89
+
90
+ export default memo(BranchNode);
@@ -0,0 +1,47 @@
1
+ import React, { memo } from 'react';
2
+ import { Handle, Position, type NodeProps } from 'reactflow';
3
+ import { Flex, Typography } from '@strapi/design-system';
4
+ import { Play } from '@strapi/icons';
5
+ import type { FlowNodeData } from '../types';
6
+ import { NODE_COLORS, NODE_DIMENSIONS } from '../constants';
7
+
8
+ const EntryNode: React.FC<NodeProps<FlowNodeData>> = ({ selected }) => {
9
+ const colors = NODE_COLORS.entry;
10
+
11
+ return (
12
+ <>
13
+ <Flex
14
+ alignItems="center"
15
+ justifyContent="center"
16
+ gap={2}
17
+ padding={3}
18
+ background="neutral0"
19
+ hasRadius
20
+ style={{
21
+ width: NODE_DIMENSIONS.width,
22
+ height: NODE_DIMENSIONS.height,
23
+ border: `2px solid ${selected ? colors.border : colors.background}`,
24
+ backgroundColor: colors.background,
25
+ boxShadow: selected ? `0 0 0 2px ${colors.border}` : 'none',
26
+ }}
27
+ >
28
+ <Play style={{ color: colors.text, width: 20, height: 20 }} />
29
+ <Typography variant="omega" fontWeight="semiBold" style={{ color: colors.text }}>
30
+ Entry Point
31
+ </Typography>
32
+ </Flex>
33
+ <Handle
34
+ type="source"
35
+ position={Position.Bottom}
36
+ style={{
37
+ background: colors.border,
38
+ width: 10,
39
+ height: 10,
40
+ border: '2px solid white',
41
+ }}
42
+ />
43
+ </>
44
+ );
45
+ };
46
+
47
+ export default memo(EntryNode);
@@ -0,0 +1,47 @@
1
+ import React, { memo } from 'react';
2
+ import { Handle, Position, type NodeProps } from 'reactflow';
3
+ import { Flex, Typography } from '@strapi/design-system';
4
+ import { Cross } from '@strapi/icons';
5
+ import type { FlowNodeData } from '../types';
6
+ import { NODE_COLORS, NODE_DIMENSIONS } from '../constants';
7
+
8
+ const ExitNode: React.FC<NodeProps<FlowNodeData>> = ({ data, selected }) => {
9
+ const colors = NODE_COLORS.exit;
10
+
11
+ return (
12
+ <>
13
+ <Handle
14
+ type="target"
15
+ position={Position.Top}
16
+ style={{
17
+ background: colors.border,
18
+ width: 10,
19
+ height: 10,
20
+ border: '2px solid white',
21
+ }}
22
+ />
23
+ <Flex
24
+ alignItems="center"
25
+ justifyContent="center"
26
+ gap={2}
27
+ padding={3}
28
+ background="neutral0"
29
+ hasRadius
30
+ style={{
31
+ width: NODE_DIMENSIONS.width,
32
+ height: NODE_DIMENSIONS.height,
33
+ border: `2px solid ${selected ? colors.border : colors.background}`,
34
+ backgroundColor: colors.background,
35
+ boxShadow: selected ? `0 0 0 2px ${colors.border}` : 'none',
36
+ }}
37
+ >
38
+ <Cross style={{ color: colors.text, width: 18, height: 18 }} />
39
+ <Typography variant="omega" fontWeight="semiBold" style={{ color: colors.text }}>
40
+ {data.name}
41
+ </Typography>
42
+ </Flex>
43
+ </>
44
+ );
45
+ };
46
+
47
+ export default memo(ExitNode);