@inspirer-dev/crm-dashboard 1.0.26 → 1.0.27

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 (38) hide show
  1. package/admin/src/components/ButtonsBuilder/index.tsx +201 -120
  2. package/admin/src/components/StepFlowBuilder/constants.ts +65 -18
  3. package/admin/src/components/StepFlowBuilder/edges/LabeledEdge.tsx +25 -18
  4. package/admin/src/components/StepFlowBuilder/flow-canvas/FlowCanvas.tsx +1 -0
  5. package/admin/src/components/StepFlowBuilder/hooks/useFlowTheme.ts +50 -0
  6. package/admin/src/components/StepFlowBuilder/nodes/BranchNode.tsx +75 -43
  7. package/admin/src/components/StepFlowBuilder/nodes/EntryNode.tsx +61 -20
  8. package/admin/src/components/StepFlowBuilder/nodes/ExitNode.tsx +55 -20
  9. package/admin/src/components/StepFlowBuilder/nodes/MessageNode.tsx +92 -39
  10. package/admin/src/components/StepFlowBuilder/nodes/WaitNode.tsx +72 -32
  11. package/admin/src/components/StepFlowBuilder/panels/BranchConfig.tsx +151 -35
  12. package/admin/src/components/StepFlowBuilder/panels/EntryConfig.tsx +305 -101
  13. package/admin/src/components/StepFlowBuilder/panels/MessageConfig.tsx +215 -89
  14. package/admin/src/components/StepFlowBuilder/panels/NodeEditPanel.tsx +139 -36
  15. package/admin/src/components/StepFlowBuilder/panels/WaitConfig.tsx +74 -35
  16. package/admin/src/components/StepFlowBuilder/toolbar/FlowToolbar.tsx +124 -28
  17. package/admin/src/components/TriggerConfigField/index.tsx +518 -195
  18. package/admin/src/pages/HomePage/components/FlowBuilderTest.tsx +151 -0
  19. package/admin/src/pages/HomePage/index.tsx +11 -1
  20. package/dist/_chunks/FlowCanvas-CyWhYUrS.mjs +2633 -0
  21. package/dist/_chunks/FlowCanvas-F9F75lCD.js +2657 -0
  22. package/dist/_chunks/index-BbzmP8mt.mjs +276 -0
  23. package/dist/_chunks/index-Bluy4IPJ.js +92 -0
  24. package/dist/_chunks/{index-XoiSAQhK.js → index-C1D94oie.js} +461 -237
  25. package/dist/_chunks/index-CNtij1ua.mjs +583 -0
  26. package/dist/_chunks/index-CRDxmdBA.js +583 -0
  27. package/dist/_chunks/{index-CWnuAWMG.mjs → index-CbWYmKPj.mjs} +226 -114
  28. package/dist/_chunks/index-ChQFn7kt.js +276 -0
  29. package/dist/_chunks/index-ChSUifMX.mjs +92 -0
  30. package/dist/admin/index.js +4 -4
  31. package/dist/admin/index.mjs +4 -4
  32. package/package.json +1 -1
  33. package/dist/_chunks/index-BdNpSOmd.js +0 -277
  34. package/dist/_chunks/index-CQJKdWYb.mjs +0 -181
  35. package/dist/_chunks/index-CVBrqZnU.js +0 -181
  36. package/dist/_chunks/index-CuPgk6KX.mjs +0 -1617
  37. package/dist/_chunks/index-DXHJL-v5.mjs +0 -277
  38. package/dist/_chunks/index-PR1uC6PJ.js +0 -1620
@@ -1,6 +1,5 @@
1
1
  import React, { forwardRef, useEffect, useMemo, useState } from 'react';
2
2
  import {
3
- Badge,
4
3
  Box,
5
4
  Button,
6
5
  Field,
@@ -9,8 +8,10 @@ import {
9
8
  TextInput,
10
9
  Typography,
11
10
  Tooltip,
11
+ Card,
12
+ CardContent,
12
13
  } from '@strapi/design-system';
13
- import { Plus, Trash, ArrowUp, ArrowDown } from '@strapi/icons';
14
+ import { Plus, Trash, ArrowUp, ArrowDown, Link as LinkIcon } from '@strapi/icons';
14
15
  import { generateId } from '../RulesBuilder/utils';
15
16
 
16
17
  type TelegramButton = {
@@ -73,6 +74,36 @@ const isValidUrl = (url: string): boolean => {
73
74
  }
74
75
  };
75
76
 
77
+ const TelegramButtonPreview: React.FC<{ text: string; url: string }> = ({ text, url }) => (
78
+ <a
79
+ href={url || '#'}
80
+ target="_blank"
81
+ rel="noopener noreferrer"
82
+ onClick={(e) => !url && e.preventDefault()}
83
+ style={{
84
+ display: 'inline-flex',
85
+ alignItems: 'center',
86
+ justifyContent: 'center',
87
+ gap: '6px',
88
+ padding: '8px 16px',
89
+ backgroundColor: '#3390ec',
90
+ color: '#ffffff',
91
+ borderRadius: '8px',
92
+ textDecoration: 'none',
93
+ fontSize: '14px',
94
+ fontWeight: 500,
95
+ cursor: url ? 'pointer' : 'default',
96
+ transition: 'background-color 0.15s ease',
97
+ minWidth: '80px',
98
+ }}
99
+ onMouseEnter={(e) => url && (e.currentTarget.style.backgroundColor = '#2b7ed8')}
100
+ onMouseLeave={(e) => (e.currentTarget.style.backgroundColor = '#3390ec')}
101
+ >
102
+ <LinkIcon width={14} height={14} style={{ opacity: 0.9 }} />
103
+ <span>{text || '(empty)'}</span>
104
+ </a>
105
+ );
106
+
76
107
  const ButtonsBuilder = forwardRef<HTMLDivElement, ButtonsBuilderProps>(
77
108
  ({ name, value, onChange, intlLabel, disabled, error, required, hint }, ref) => {
78
109
  const [buttons, setButtons] = useState<TelegramButton[]>(() => parseButtons(value));
@@ -122,133 +153,184 @@ const ButtonsBuilder = forwardRef<HTMLDivElement, ButtonsBuilderProps>(
122
153
 
123
154
  return (
124
155
  <Field.Root name={name} error={error} required={required} hint={hint} ref={ref}>
125
- <Flex direction="column" gap={3}>
126
- <Field.Label>{intlLabel?.defaultMessage || 'Buttons'}</Field.Label>
127
- <Typography variant="pi" textColor="neutral600">
128
- Build Telegram inline keyboard buttons (text + URL). Stored as JSON.
129
- </Typography>
130
-
131
- <Flex direction="column" gap={2}>
132
- {buttons.map((btn, idx) => {
133
- const urlOk = btn.url.length === 0 ? true : isValidUrl(btn.url);
134
- return (
135
- <Flex
136
- key={btn.id}
137
- gap={2}
138
- alignItems="flex-start"
139
- padding={3}
140
- background="neutral0"
141
- hasRadius
142
- style={{ border: '1px solid #dcdce4' }}
143
- >
144
- <Box style={{ flex: 2, minWidth: 180 }}>
145
- <Field.Root name={`${name}.${btn.id}.text`}>
146
- <Field.Label>Text</Field.Label>
147
- <TextInput
148
- value={btn.text}
149
- onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
150
- updateButton(btn.id, { text: e.target.value })
151
- }
152
- disabled={disabled}
153
- />
154
- </Field.Root>
155
- </Box>
156
+ <Flex direction="column" gap={4}>
157
+ <Flex justifyContent="space-between" alignItems="center">
158
+ <Box>
159
+ <Field.Label>{intlLabel?.defaultMessage || 'Buttons'}</Field.Label>
160
+ <Typography variant="pi" textColor="neutral500">
161
+ Telegram inline keyboard buttons
162
+ </Typography>
163
+ </Box>
164
+ <Button
165
+ startIcon={<Plus />}
166
+ onClick={addButton}
167
+ disabled={disabled}
168
+ variant="secondary"
169
+ size="S"
170
+ >
171
+ Add
172
+ </Button>
173
+ </Flex>
156
174
 
157
- <Box style={{ flex: 3, minWidth: 220 }}>
158
- <Field.Root name={`${name}.${btn.id}.url`}>
159
- <Field.Label>URL</Field.Label>
160
- <TextInput
161
- value={btn.url}
162
- onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
163
- updateButton(btn.id, { url: e.target.value })
164
- }
165
- disabled={disabled}
166
- />
167
- {!urlOk && (
168
- <Typography variant="pi" textColor="danger600" style={{ marginTop: 4 }}>
169
- Please enter a valid http/https URL
170
- </Typography>
171
- )}
172
- </Field.Root>
173
- </Box>
175
+ {buttons.length === 0 ? (
176
+ <Box
177
+ padding={6}
178
+ background="neutral100"
179
+ hasRadius
180
+ style={{
181
+ border: '2px dashed #dcdce4',
182
+ textAlign: 'center',
183
+ }}
184
+ >
185
+ <Typography variant="omega" textColor="neutral500">
186
+ No buttons yet
187
+ </Typography>
188
+ <Typography variant="pi" textColor="neutral400" style={{ display: 'block', marginTop: 4 }}>
189
+ Add a button to create an inline keyboard for your Telegram message
190
+ </Typography>
191
+ </Box>
192
+ ) : (
193
+ <Flex direction="column" gap={2}>
194
+ {buttons.map((btn, idx) => {
195
+ const urlOk = btn.url.length === 0 ? true : isValidUrl(btn.url);
196
+ return (
197
+ <Card
198
+ key={btn.id}
199
+ background="neutral0"
200
+ style={{
201
+ border: urlOk ? '1px solid #eaeaef' : '1px solid #ee5e52',
202
+ transition: 'box-shadow 0.15s ease',
203
+ }}
204
+ >
205
+ <CardContent>
206
+ <Flex gap={3} alignItems="center" padding={3}>
207
+ <Box
208
+ style={{
209
+ width: '24px',
210
+ height: '24px',
211
+ borderRadius: '4px',
212
+ backgroundColor: '#f0f0ff',
213
+ display: 'flex',
214
+ alignItems: 'center',
215
+ justifyContent: 'center',
216
+ flexShrink: 0,
217
+ }}
218
+ >
219
+ <Typography variant="sigma" textColor="primary600">
220
+ {idx + 1}
221
+ </Typography>
222
+ </Box>
174
223
 
175
- <Box style={{ width: 90 }}>
176
- <Field.Root name={`${name}.${btn.id}.row`}>
177
- <Field.Label>Row</Field.Label>
178
- <TextInput
179
- type="number"
180
- value={String(btn.row ?? 0)}
181
- onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
182
- updateButton(btn.id, { row: parseInt(e.target.value, 10) || 0 })
183
- }
184
- disabled={disabled}
185
- />
186
- </Field.Root>
187
- </Box>
224
+ <Box style={{ flex: 1, minWidth: 120 }}>
225
+ <TextInput
226
+ placeholder="Button text"
227
+ value={btn.text}
228
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
229
+ updateButton(btn.id, { text: e.target.value })
230
+ }
231
+ disabled={disabled}
232
+ size="S"
233
+ />
234
+ </Box>
188
235
 
189
- <Flex direction="column" gap={1} paddingTop={6}>
190
- <Tooltip label="Move up">
191
- <IconButton
192
- onClick={() => move(idx, idx - 1)}
193
- label="Move up"
194
- variant="ghost"
195
- disabled={disabled || idx === 0}
196
- >
197
- <ArrowUp />
198
- </IconButton>
199
- </Tooltip>
200
- <Tooltip label="Move down">
201
- <IconButton
202
- onClick={() => move(idx, idx + 1)}
203
- label="Move down"
204
- variant="ghost"
205
- disabled={disabled || idx === buttons.length - 1}
206
- >
207
- <ArrowDown />
208
- </IconButton>
209
- </Tooltip>
210
- <Tooltip label="Delete button">
211
- <IconButton
212
- onClick={() => deleteButton(btn.id)}
213
- label="Delete button"
214
- variant="ghost"
215
- disabled={disabled}
216
- >
217
- <Trash />
218
- </IconButton>
219
- </Tooltip>
220
- </Flex>
221
- </Flex>
222
- );
223
- })}
236
+ <Box style={{ flex: 2, minWidth: 180 }}>
237
+ <TextInput
238
+ placeholder="https://..."
239
+ value={btn.url}
240
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
241
+ updateButton(btn.id, { url: e.target.value })
242
+ }
243
+ disabled={disabled}
244
+ size="S"
245
+ hasError={!urlOk}
246
+ />
247
+ </Box>
224
248
 
225
- {buttons.length === 0 && (
226
- <Box padding={4} background="neutral0" hasRadius style={{ border: '1px dashed #dcdce4' }}>
227
- <Typography variant="omega" textColor="neutral600">
228
- No buttons. Add one to create an inline keyboard.
229
- </Typography>
230
- </Box>
231
- )}
232
- </Flex>
249
+ <Box style={{ width: 60 }}>
250
+ <TextInput
251
+ type="number"
252
+ placeholder="Row"
253
+ value={String(btn.row ?? 0)}
254
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
255
+ updateButton(btn.id, { row: parseInt(e.target.value, 10) || 0 })
256
+ }
257
+ disabled={disabled}
258
+ size="S"
259
+ />
260
+ </Box>
233
261
 
234
- <Flex gap={2}>
235
- <Button startIcon={<Plus />} onClick={addButton} disabled={disabled} variant="secondary">
236
- Add button
237
- </Button>
238
- </Flex>
262
+ <Flex gap={1} style={{ flexShrink: 0 }}>
263
+ <Tooltip label="Move up">
264
+ <IconButton
265
+ onClick={() => move(idx, idx - 1)}
266
+ label="Move up"
267
+ variant="ghost"
268
+ size="S"
269
+ disabled={disabled || idx === 0}
270
+ >
271
+ <ArrowUp width={16} height={16} />
272
+ </IconButton>
273
+ </Tooltip>
274
+ <Tooltip label="Move down">
275
+ <IconButton
276
+ onClick={() => move(idx, idx + 1)}
277
+ label="Move down"
278
+ variant="ghost"
279
+ size="S"
280
+ disabled={disabled || idx === buttons.length - 1}
281
+ >
282
+ <ArrowDown width={16} height={16} />
283
+ </IconButton>
284
+ </Tooltip>
285
+ <Tooltip label="Delete">
286
+ <IconButton
287
+ onClick={() => deleteButton(btn.id)}
288
+ label="Delete"
289
+ variant="ghost"
290
+ size="S"
291
+ disabled={disabled}
292
+ style={{ color: '#d02b20' }}
293
+ >
294
+ <Trash width={16} height={16} />
295
+ </IconButton>
296
+ </Tooltip>
297
+ </Flex>
298
+ </Flex>
299
+ {!urlOk && (
300
+ <Box paddingLeft={3} paddingBottom={2}>
301
+ <Typography variant="pi" textColor="danger600">
302
+ Please enter a valid http/https URL
303
+ </Typography>
304
+ </Box>
305
+ )}
306
+ </CardContent>
307
+ </Card>
308
+ );
309
+ })}
310
+ </Flex>
311
+ )}
239
312
 
240
313
  {buttons.length > 0 && (
241
- <Box paddingTop={2}>
242
- <Typography variant="pi" textColor="neutral600" style={{ marginBottom: 8, display: 'block' }}>
243
- Preview
314
+ <Box
315
+ padding={4}
316
+ background="neutral800"
317
+ hasRadius
318
+ style={{
319
+ backgroundImage: 'linear-gradient(135deg, #1e3a5f 0%, #0e2439 100%)',
320
+ }}
321
+ >
322
+ <Typography
323
+ variant="sigma"
324
+ textColor="neutral0"
325
+ style={{ display: 'block', marginBottom: 12, opacity: 0.7 }}
326
+ >
327
+ PREVIEW
244
328
  </Typography>
245
- <Flex direction="column" gap={2}>
329
+ <Flex direction="column" gap={2} alignItems="center">
246
330
  {previewRows.map((row) => (
247
- <Flex key={row.row} gap={1} wrap="wrap">
331
+ <Flex key={row.row} gap={2} wrap="wrap" justifyContent="center">
248
332
  {row.items.map((b) => (
249
- <Badge key={b.id} backgroundColor="primary100" textColor="primary600">
250
- {b.text || '(empty)'}
251
- </Badge>
333
+ <TelegramButtonPreview key={b.id} text={b.text} url={b.url} />
252
334
  ))}
253
335
  </Flex>
254
336
  ))}
@@ -267,4 +349,3 @@ const ButtonsBuilder = forwardRef<HTMLDivElement, ButtonsBuilderProps>(
267
349
  ButtonsBuilder.displayName = 'ButtonsBuilder';
268
350
 
269
351
  export default ButtonsBuilder;
270
-
@@ -2,38 +2,85 @@ import type { StepType, ChannelType, DurationUnit } from './types';
2
2
 
3
3
  export const NODE_COLORS: Record<
4
4
  StepType,
5
- { background: string; border: string; text: string }
5
+ {
6
+ background: string;
7
+ border: string;
8
+ text: string;
9
+ gradient: string;
10
+ glow: string;
11
+ icon: string;
12
+ }
6
13
  > = {
7
14
  entry: {
8
- background: '#eef5eb',
9
- border: '#5cb176',
10
- text: '#297c3b',
15
+ background: 'linear-gradient(135deg, #10b981 0%, #059669 100%)',
16
+ border: '#059669',
17
+ text: '#ffffff',
18
+ gradient: 'linear-gradient(135deg, #d1fae5 0%, #a7f3d0 100%)',
19
+ glow: 'rgba(16, 185, 129, 0.25)',
20
+ icon: '#059669',
11
21
  },
12
22
  message: {
13
- background: '#eef8ff',
14
- border: '#0077cc',
15
- text: '#0055a4',
23
+ background: 'linear-gradient(135deg, #3b82f6 0%, #2563eb 100%)',
24
+ border: '#2563eb',
25
+ text: '#ffffff',
26
+ gradient: 'linear-gradient(135deg, #dbeafe 0%, #bfdbfe 100%)',
27
+ glow: 'rgba(59, 130, 246, 0.25)',
28
+ icon: '#2563eb',
16
29
  },
17
30
  wait: {
18
- background: '#fffae6',
19
- border: '#e9b200',
20
- text: '#a16207',
31
+ background: 'linear-gradient(135deg, #f59e0b 0%, #d97706 100%)',
32
+ border: '#d97706',
33
+ text: '#ffffff',
34
+ gradient: 'linear-gradient(135deg, #fef3c7 0%, #fde68a 100%)',
35
+ glow: 'rgba(245, 158, 11, 0.25)',
36
+ icon: '#d97706',
21
37
  },
22
38
  branch: {
23
- background: '#f5f0ff',
24
- border: '#7b61ff',
25
- text: '#5746af',
39
+ background: 'linear-gradient(135deg, #8b5cf6 0%, #7c3aed 100%)',
40
+ border: '#7c3aed',
41
+ text: '#ffffff',
42
+ gradient: 'linear-gradient(135deg, #ede9fe 0%, #ddd6fe 100%)',
43
+ glow: 'rgba(139, 92, 246, 0.25)',
44
+ icon: '#7c3aed',
26
45
  },
27
46
  exit: {
28
- background: '#fef1f0',
29
- border: '#dc2626',
30
- text: '#c03030',
47
+ background: 'linear-gradient(135deg, #6b7280 0%, #4b5563 100%)',
48
+ border: '#4b5563',
49
+ text: '#ffffff',
50
+ gradient: 'linear-gradient(135deg, #f3f4f6 0%, #e5e7eb 100%)',
51
+ glow: 'rgba(107, 114, 128, 0.25)',
52
+ icon: '#4b5563',
53
+ },
54
+ };
55
+
56
+ export const EDGE_COLORS = {
57
+ default: {
58
+ stroke: '#94a3b8',
59
+ strokeWidth: 2,
60
+ },
61
+ yes: {
62
+ stroke: '#10b981',
63
+ strokeWidth: 2,
64
+ label: {
65
+ background: '#d1fae5',
66
+ text: '#059669',
67
+ border: '#a7f3d0',
68
+ },
69
+ },
70
+ no: {
71
+ stroke: '#ef4444',
72
+ strokeWidth: 2,
73
+ label: {
74
+ background: '#fee2e2',
75
+ text: '#dc2626',
76
+ border: '#fecaca',
77
+ },
31
78
  },
32
79
  };
33
80
 
34
81
  export const NODE_DIMENSIONS = {
35
- width: 220,
36
- height: 80,
82
+ width: 240,
83
+ height: 88,
37
84
  };
38
85
 
39
86
  export const STEP_TYPE_LABELS: Record<StepType, string> = {
@@ -2,16 +2,16 @@ import React from 'react';
2
2
  import {
3
3
  BaseEdge,
4
4
  EdgeLabelRenderer,
5
- getBezierPath,
5
+ getSmoothStepPath,
6
6
  type EdgeProps,
7
7
  } from 'reactflow';
8
+ import { EDGE_COLORS } from '../constants';
8
9
 
9
10
  interface LabeledEdgeData {
10
11
  label?: string;
11
12
  }
12
13
 
13
14
  const LabeledEdge: React.FC<EdgeProps<LabeledEdgeData>> = ({
14
- id,
15
15
  sourceX,
16
16
  sourceY,
17
17
  targetX,
@@ -22,21 +22,26 @@ const LabeledEdge: React.FC<EdgeProps<LabeledEdgeData>> = ({
22
22
  style = {},
23
23
  markerEnd,
24
24
  }) => {
25
- const [edgePath, labelX, labelY] = getBezierPath({
25
+ const [edgePath] = getSmoothStepPath({
26
26
  sourceX,
27
27
  sourceY,
28
28
  sourcePosition,
29
29
  targetX,
30
30
  targetY,
31
31
  targetPosition,
32
+ borderRadius: 16,
32
33
  });
33
34
 
34
35
  const isYes = sourceHandle === 'yes';
35
36
  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';
37
+ const label = isYes ? 'YES' : isNo ? 'NO' : null;
38
+
39
+ const edgeStyle = isYes ? EDGE_COLORS.yes : isNo ? EDGE_COLORS.no : EDGE_COLORS.default;
40
+
41
+ const labelStyle = isYes ? EDGE_COLORS.yes.label : isNo ? EDGE_COLORS.no.label : null;
42
+
43
+ const labelPosX = sourceX;
44
+ const labelPosY = sourceY + 24;
40
45
 
41
46
  return (
42
47
  <>
@@ -45,23 +50,25 @@ const LabeledEdge: React.FC<EdgeProps<LabeledEdgeData>> = ({
45
50
  markerEnd={markerEnd}
46
51
  style={{
47
52
  ...style,
48
- stroke: isYes ? '#5cb176' : isNo ? '#dc2626' : '#b1b1b7',
49
- strokeWidth: 2,
53
+ stroke: edgeStyle.stroke,
54
+ strokeWidth: edgeStyle.strokeWidth,
50
55
  }}
51
56
  />
52
- {label && (
57
+ {label && labelStyle && (
53
58
  <EdgeLabelRenderer>
54
59
  <div
55
60
  style={{
56
61
  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}`,
62
+ transform: `translate(-50%, -50%) translate(${labelPosX}px, ${labelPosY}px)`,
63
+ fontSize: 10,
64
+ fontWeight: 700,
65
+ letterSpacing: '0.05em',
66
+ background: labelStyle.background,
67
+ color: labelStyle.text,
68
+ padding: '4px 12px',
69
+ borderRadius: 6,
70
+ border: `2px solid ${labelStyle.border}`,
71
+ boxShadow: '0 2px 8px rgba(0,0,0,0.15)',
65
72
  pointerEvents: 'all',
66
73
  }}
67
74
  className="nodrag nopan"
@@ -220,6 +220,7 @@ const FlowCanvas: React.FC<FlowCanvasProps> = ({ steps, onStepsChange, disabled,
220
220
  <Box
221
221
  style={{
222
222
  height: '100%',
223
+ width: '100%',
223
224
  border: error ? '1px solid #ee5e52' : '1px solid #dcdce4',
224
225
  borderRadius: 4,
225
226
  overflow: 'hidden',
@@ -0,0 +1,50 @@
1
+ import { useTheme } from 'styled-components';
2
+
3
+ interface StrapiTheme {
4
+ colors?: Record<string, string>;
5
+ }
6
+
7
+ export interface FlowThemeColors {
8
+ background: string;
9
+ surface: string;
10
+ surfaceHover: string;
11
+ border: string;
12
+ borderLight: string;
13
+ text: string;
14
+ textMuted: string;
15
+ textSubtle: string;
16
+ isDark: boolean;
17
+ }
18
+
19
+ export const useFlowTheme = (): FlowThemeColors => {
20
+ const theme = useTheme() as StrapiTheme;
21
+ const colors = theme?.colors;
22
+
23
+ const isDark = colors?.neutral0 === '#212134';
24
+
25
+ if (isDark) {
26
+ return {
27
+ background: '#212134',
28
+ surface: '#262639',
29
+ surfaceHover: '#32324d',
30
+ border: '#4a4a6a',
31
+ borderLight: '#3d3d57',
32
+ text: '#ffffff',
33
+ textMuted: '#a5a5ba',
34
+ textSubtle: '#8e8ea9',
35
+ isDark: true,
36
+ };
37
+ }
38
+
39
+ return {
40
+ background: '#ffffff',
41
+ surface: '#f8fafc',
42
+ surfaceHover: '#f1f5f9',
43
+ border: '#e2e8f0',
44
+ borderLight: '#f1f5f9',
45
+ text: '#1e293b',
46
+ textMuted: '#64748b',
47
+ textSubtle: '#94a3b8',
48
+ isDark: false,
49
+ };
50
+ };