@inspirer-dev/crm-dashboard 1.0.80 → 1.0.82

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 (46) hide show
  1. package/admin/src/components/ButtonsBuilder/index.tsx +3 -3
  2. package/admin/src/components/CancelConditionsField/index.tsx +7 -6
  3. package/admin/src/components/RulesBuilder/index.tsx +9 -8
  4. package/admin/src/components/StepFlowBuilder/constants.ts +2 -0
  5. package/admin/src/components/StepFlowBuilder/index.tsx +1 -1
  6. package/admin/src/components/StepFlowBuilder/nodes/EventTriggerNode.tsx +29 -1
  7. package/admin/src/components/StepFlowBuilder/panels/EventTriggerConfig.tsx +97 -9
  8. package/admin/src/components/StepFlowBuilder/panels/NodeEditPanel.tsx +1 -1
  9. package/admin/src/components/StepFlowBuilder/panels/WaitConfig.tsx +1 -3
  10. package/admin/src/components/StepFlowBuilder/types.ts +2 -0
  11. package/admin/src/components/TriggerConfigField/index.tsx +11 -10
  12. package/admin/src/components/TriggerParamsField/constants.ts +109 -0
  13. package/admin/src/components/TriggerParamsField/index.tsx +292 -0
  14. package/admin/src/index.ts +26 -1
  15. package/admin/src/pages/HomePage/components/CampaignBuilder/index.tsx +4 -0
  16. package/admin/src/pages/HomePage/components/FlowBuilderTest.tsx +3 -3
  17. package/admin/src/pages/HomePage/components/SendTimeHeatMap.tsx +2 -2
  18. package/admin/src/pages/HomePage/components/StatsView.tsx +2 -2
  19. package/admin/src/translations/en.json +4 -1
  20. package/admin/src/translations/ru.json +4 -1
  21. package/admin/tsconfig.json +12 -1
  22. package/admin/tsconfig.tsbuildinfo +1 -1
  23. package/dist/_chunks/{en-DEUgX5uV.mjs → en-B5BgIROW.mjs} +3 -1
  24. package/dist/_chunks/{en-D2kTkBns.js → en-GKZo4lia.js} +3 -1
  25. package/dist/_chunks/{index-C-1xCfqJ.js → index-354YMebI.js} +152 -38
  26. package/dist/_chunks/{index-BydXrDhA.mjs → index-BKfFI_Jo.mjs} +2 -2
  27. package/dist/_chunks/{index-D1kMmlrA.js → index-BQcRoIJr.js} +5 -4
  28. package/dist/_chunks/{index-BJzk6xNb.mjs → index-BgwX1xYS.mjs} +7 -6
  29. package/dist/_chunks/{index-pJUyH-Qr.js → index-Cg0G_bTE.js} +2 -2
  30. package/dist/_chunks/index-CuMY0eo5.js +310 -0
  31. package/dist/_chunks/{index-unxa4Q_H.mjs → index-DKJtyGq7.mjs} +5 -4
  32. package/dist/_chunks/{index-BFRbyVHC.js → index-DRXMKPXI.js} +1 -1
  33. package/dist/_chunks/{index-Xa_4jez0.mjs → index-Dhj0KzCX.mjs} +5 -4
  34. package/dist/_chunks/{index-Dv1tGmDT.mjs → index-LXBoz7PC.mjs} +1 -1
  35. package/dist/_chunks/index-NvFKi6er.mjs +310 -0
  36. package/dist/_chunks/{index-BKrTVHBr.js → index-T7VXjklK.js} +5 -4
  37. package/dist/_chunks/{index-CWSibOAr.mjs → index-gR86Z3uh.mjs} +152 -38
  38. package/dist/_chunks/{index-CAwynvu7.js → index-xDSMnUMp.js} +7 -6
  39. package/dist/_chunks/{ru-BKzplvmu.js → ru-bj5iiIPr.js} +3 -1
  40. package/dist/_chunks/{ru-DOt1yfNm.mjs → ru-h22ZdPCS.mjs} +3 -1
  41. package/dist/admin/index.js +31 -7
  42. package/dist/admin/index.mjs +32 -8
  43. package/dist/server/index.js +5 -0
  44. package/dist/server/index.mjs +5 -0
  45. package/package.json +3 -3
  46. package/server/src/register.ts +6 -0
@@ -80,7 +80,7 @@ interface StrapiTheme {
80
80
  }
81
81
 
82
82
  const useThemeColors = () => {
83
- const theme = useTheme() as StrapiTheme;
83
+ const theme = useTheme() as unknown as StrapiTheme;
84
84
  const isDark = theme?.colors?.neutral0 === '#212134';
85
85
 
86
86
  return useMemo(() => ({
@@ -367,8 +367,8 @@ const ButtonsBuilder = forwardRef<HTMLDivElement, ButtonsBuilderProps>(
367
367
  </Box>
368
368
  )}
369
369
 
370
- {error && <Field.Error>{error}</Field.Error>}
371
- {hint && <Field.Hint>{hint}</Field.Hint>}
370
+ {error && <Field.Error />}
371
+ {hint && <Field.Hint />}
372
372
  </Flex>
373
373
  </Field.Root>
374
374
  );
@@ -116,7 +116,8 @@ const ValueInput: React.FC<ValueInputProps> = ({ metric, value, operator, onChan
116
116
  return (
117
117
  <Flex gap={2} alignItems="center">
118
118
  <Switch
119
- label=""
119
+ onLabel=""
120
+ offLabel=""
120
121
  checked={boolVal}
121
122
  onChange={() => onChange(!boolVal)}
122
123
  disabled={disabled}
@@ -161,7 +162,7 @@ const ValueInput: React.FC<ValueInputProps> = ({ metric, value, operator, onChan
161
162
  <Box style={{ width: '120px' }}>
162
163
  <SingleSelect
163
164
  value={unit}
164
- onChange={(newUnit: string) => onChange({ [newUnit]: numValue })}
165
+ onChange={(newUnit: string | number) => onChange({ [String(newUnit)]: numValue })}
165
166
  disabled={disabled}
166
167
  >
167
168
  {CANCEL_TIME_UNITS.map((u) => (
@@ -215,7 +216,7 @@ const RuleRow: React.FC<RuleRowProps> = ({ rule, onUpdate, onDelete, disabled })
215
216
  <Box style={{ flex: '1 1 200px', minWidth: '180px' }}>
216
217
  <SingleSelect
217
218
  value={rule.field}
218
- onChange={(val: string) => handleFieldChange(val)}
219
+ onChange={(val: string | number) => handleFieldChange(String(val))}
219
220
  disabled={disabled}
220
221
  size="S"
221
222
  >
@@ -230,7 +231,7 @@ const RuleRow: React.FC<RuleRowProps> = ({ rule, onUpdate, onDelete, disabled })
230
231
  <Box style={{ width: '80px' }}>
231
232
  <SingleSelect
232
233
  value={rule.operator}
233
- onChange={(val: string) => onUpdate({ ...rule, operator: val as RuleOperator })}
234
+ onChange={(val: string | number) => onUpdate({ ...rule, operator: String(val) as RuleOperator })}
234
235
  disabled={disabled}
235
236
  size="S"
236
237
  >
@@ -386,8 +387,8 @@ const CancelConditionsField = forwardRef<HTMLDivElement, CancelConditionsFieldPr
386
387
  </CardContent>
387
388
  </Card>
388
389
 
389
- {error && <Field.Error>{error}</Field.Error>}
390
- {hint && <Field.Hint>{hint}</Field.Hint>}
390
+ {error && <Field.Error />}
391
+ {hint && <Field.Hint />}
391
392
  </Flex>
392
393
  </Field.Root>
393
394
  );
@@ -58,7 +58,7 @@ interface StrapiTheme {
58
58
  }
59
59
 
60
60
  const useThemeColors = () => {
61
- const theme = useTheme() as StrapiTheme;
61
+ const theme = useTheme() as unknown as StrapiTheme;
62
62
  const isDark = theme?.colors?.neutral0 === '#212134';
63
63
 
64
64
  return useMemo(
@@ -124,8 +124,9 @@ const ValueInput: React.FC<ValueInputProps> = ({
124
124
  return (
125
125
  <Flex gap={2} alignItems="center">
126
126
  <Switch
127
- label=""
128
- selected={boolVal}
127
+ onLabel=""
128
+ offLabel=""
129
+ checked={boolVal}
129
130
  onChange={() => onChange(!boolVal)}
130
131
  disabled={disabled}
131
132
  />
@@ -141,7 +142,7 @@ const ValueInput: React.FC<ValueInputProps> = ({
141
142
  return (
142
143
  <SingleSelect
143
144
  value={boolVal ? 'true' : 'false'}
144
- onChange={(val: string) => onChange(val === 'true')}
145
+ onChange={(val: string | number) => onChange(val === 'true')}
145
146
  disabled={disabled}
146
147
  size="S"
147
148
  >
@@ -176,7 +177,7 @@ const ValueInput: React.FC<ValueInputProps> = ({
176
177
  <Box style={{ width: '120px' }}>
177
178
  <SingleSelect
178
179
  value={unit}
179
- onChange={(val: string) => onChange(createTimeAgoValue(amount, val))}
180
+ onChange={(val: string | number) => onChange(createTimeAgoValue(amount, String(val)))}
180
181
  disabled={disabled}
181
182
  size="S"
182
183
  >
@@ -195,7 +196,7 @@ const ValueInput: React.FC<ValueInputProps> = ({
195
196
  return (
196
197
  <SingleSelect
197
198
  value={String(value || '')}
198
- onChange={(val: string) => onChange(val)}
199
+ onChange={(val: string | number) => onChange(val)}
199
200
  disabled={disabled}
200
201
  size="S"
201
202
  >
@@ -328,7 +329,7 @@ const RuleRow: React.FC<RuleRowProps> = ({ rule, onUpdate, onDelete, disabled })
328
329
  <Box style={{ flex: '1 1 200px', minWidth: '180px' }}>
329
330
  <SingleSelect
330
331
  value={rule.field}
331
- onChange={(val: string) => handleFieldChange(val)}
332
+ onChange={(val: string | number) => handleFieldChange(String(val))}
332
333
  disabled={disabled}
333
334
  size="S"
334
335
  >
@@ -350,7 +351,7 @@ const RuleRow: React.FC<RuleRowProps> = ({ rule, onUpdate, onDelete, disabled })
350
351
  <Box style={{ width: '75px' }}>
351
352
  <SingleSelect
352
353
  value={rule.operator}
353
- onChange={(val: string) => handleOperatorChange(val as RuleOperator)}
354
+ onChange={(val: string | number) => handleOperatorChange(val as RuleOperator)}
354
355
  disabled={disabled}
355
356
  size="S"
356
357
  >
@@ -152,6 +152,8 @@ export const DEFAULT_NODE_DATA: Record<StepType, { name: string; config: object
152
152
  eventTrigger: {
153
153
  events: [],
154
154
  logic: 'or',
155
+ timeout: 0,
156
+ timeoutUnit: 'minutes' as DurationUnit,
155
157
  },
156
158
  },
157
159
  },
@@ -474,7 +474,7 @@ const StepFlowBuilderInner = forwardRef<HTMLDivElement, StepFlowBuilderProps>(
474
474
  const stepCount = steps.length;
475
475
 
476
476
  const validationError = useMemo(() => {
477
- if (!validation || validation.isValid) return null;
477
+ if (!validation || validation.isValid) return undefined;
478
478
  const errorCount = validation.errors.length;
479
479
  const errorWord = pluralize(errorCount, 'ошибка', 'ошибки', 'ошибок');
480
480
  return `Поток содержит ${errorCount} ${errorWord} валидации. Исправьте все ошибки перед публикацией.`;
@@ -11,6 +11,8 @@ const EventTriggerNode: React.FC<NodeProps<FlowNodeData>> = ({ id, data, selecte
11
11
  const eventTrigger = data.config.eventTrigger;
12
12
  const events = eventTrigger?.events || [];
13
13
  const logic = eventTrigger?.logic || 'or';
14
+ const timeout = eventTrigger?.timeout || 0;
15
+ const timeoutUnit = eventTrigger?.timeoutUnit || 'minutes';
14
16
  const { hasError, hasWarning } = useNodeValidation(id);
15
17
 
16
18
  const getEventLabel = () => {
@@ -19,6 +21,17 @@ const EventTriggerNode: React.FC<NodeProps<FlowNodeData>> = ({ id, data, selecte
19
21
  return `${events.length} events (${logic.toUpperCase()})`;
20
22
  };
21
23
 
24
+ const getTimeoutLabel = () => {
25
+ if (timeout <= 0) return null;
26
+ const unitShort: Record<string, string> = {
27
+ seconds: 's',
28
+ minutes: 'm',
29
+ hours: 'h',
30
+ days: 'd',
31
+ };
32
+ return `${timeout}${unitShort[timeoutUnit] || timeoutUnit}`;
33
+ };
34
+
22
35
  return (
23
36
  <div
24
37
  style={{
@@ -106,7 +119,7 @@ const EventTriggerNode: React.FC<NodeProps<FlowNodeData>> = ({ id, data, selecte
106
119
  background: 'rgba(255, 255, 255, 0.9)',
107
120
  padding: '2px 10px',
108
121
  borderRadius: 4,
109
- maxWidth: 160,
122
+ maxWidth: getTimeoutLabel() ? 120 : 160,
110
123
  overflow: 'hidden',
111
124
  textOverflow: 'ellipsis',
112
125
  whiteSpace: 'nowrap',
@@ -114,6 +127,21 @@ const EventTriggerNode: React.FC<NodeProps<FlowNodeData>> = ({ id, data, selecte
114
127
  >
115
128
  {getEventLabel()}
116
129
  </span>
130
+ {getTimeoutLabel() && (
131
+ <span
132
+ style={{
133
+ fontSize: 10,
134
+ fontWeight: 600,
135
+ color: '#d97706',
136
+ background: 'rgba(255, 255, 255, 0.9)',
137
+ padding: '2px 8px',
138
+ borderRadius: 4,
139
+ whiteSpace: 'nowrap',
140
+ }}
141
+ >
142
+ ⏱ {getTimeoutLabel()}
143
+ </span>
144
+ )}
117
145
  </div>
118
146
  </div>
119
147
  <Handle
@@ -7,8 +7,12 @@ import {
7
7
  ComboboxOption,
8
8
  Badge,
9
9
  Checkbox,
10
+ NumberInput,
11
+ SingleSelect,
12
+ SingleSelectOption,
10
13
  } from '@strapi/design-system';
11
- import type { FlowNodeData, EventTriggerLogic } from '../types';
14
+ import type { FlowNodeData, EventTriggerLogic, DurationUnit } from '../types';
15
+ import { DURATION_UNIT_OPTIONS } from '../constants';
12
16
  import { useFlowTheme } from '../hooks/useFlowTheme';
13
17
 
14
18
  const ANALYTICS_EVENTS: Record<string, { value: string; label: string }[]> = {
@@ -81,6 +85,8 @@ const EventTriggerConfig: React.FC<EventTriggerConfigProps> = ({ data, onUpdate,
81
85
  const eventTrigger = data.config.eventTrigger || { events: [], logic: 'or' as EventTriggerLogic };
82
86
  const events = eventTrigger.events || [];
83
87
  const logic = eventTrigger.logic || 'or';
88
+ const timeout = eventTrigger.timeout || 0;
89
+ const timeoutUnit = eventTrigger.timeoutUnit || 'minutes';
84
90
 
85
91
  const [inputText, setInputText] = useState('');
86
92
  const justSelectedRef = useRef(false);
@@ -135,6 +141,30 @@ const EventTriggerConfig: React.FC<EventTriggerConfigProps> = ({ data, onUpdate,
135
141
  });
136
142
  };
137
143
 
144
+ const handleTimeoutChange = (value: number | undefined) => {
145
+ onUpdate({
146
+ config: {
147
+ ...data.config,
148
+ eventTrigger: {
149
+ ...eventTrigger,
150
+ timeout: value || 0,
151
+ },
152
+ },
153
+ });
154
+ };
155
+
156
+ const handleTimeoutUnitChange = (value: string) => {
157
+ onUpdate({
158
+ config: {
159
+ ...data.config,
160
+ eventTrigger: {
161
+ ...eventTrigger,
162
+ timeoutUnit: value as DurationUnit,
163
+ },
164
+ },
165
+ });
166
+ };
167
+
138
168
  return (
139
169
  <Flex direction="column" gap={4}>
140
170
  <Box style={{ width: '100%' }}>
@@ -168,7 +198,8 @@ const EventTriggerConfig: React.FC<EventTriggerConfigProps> = ({ data, onUpdate,
168
198
  disabled={disabled}
169
199
  creatable
170
200
  createMessage={(v: string) => `Использовать: "${v}"`}
171
- onCreateOption={(v: string) => {
201
+ onCreateOption={(v?: string) => {
202
+ if (!v) return;
172
203
  handleAddEvent(v);
173
204
  }}
174
205
  >
@@ -322,6 +353,53 @@ const EventTriggerConfig: React.FC<EventTriggerConfigProps> = ({ data, onUpdate,
322
353
  </Box>
323
354
  )}
324
355
 
356
+ <Box style={{ width: '100%' }}>
357
+ <Typography
358
+ variant="sigma"
359
+ style={{
360
+ marginBottom: 10,
361
+ display: 'block',
362
+ fontSize: 11,
363
+ letterSpacing: '0.05em',
364
+ color: theme.textMuted,
365
+ }}
366
+ >
367
+ ТАЙМАУТ
368
+ </Typography>
369
+ <Flex gap={3} alignItems="center">
370
+ <Box style={{ flex: 1 }}>
371
+ <NumberInput
372
+ value={timeout}
373
+ onValueChange={handleTimeoutChange}
374
+ disabled={disabled}
375
+ step={1}
376
+ />
377
+ </Box>
378
+ <Box style={{ width: 140 }}>
379
+ <SingleSelect
380
+ value={timeoutUnit}
381
+ onChange={(val: string | number) => handleTimeoutUnitChange(String(val))}
382
+ disabled={disabled}
383
+ >
384
+ {DURATION_UNIT_OPTIONS.map((opt) => (
385
+ <SingleSelectOption key={opt.value} value={opt.value}>
386
+ {opt.label}
387
+ </SingleSelectOption>
388
+ ))}
389
+ </SingleSelect>
390
+ </Box>
391
+ </Flex>
392
+ <Typography
393
+ variant="pi"
394
+ textColor="neutral500"
395
+ style={{ marginTop: 8, display: 'block' }}
396
+ >
397
+ {timeout > 0
398
+ ? `Если событие не произойдёт за ${timeout} ${timeoutUnit}, кампания продолжится автоматически`
399
+ : 'Установите 0 для бесконечного ожидания события'}
400
+ </Typography>
401
+ </Box>
402
+
325
403
  <div
326
404
  style={{
327
405
  padding: 14,
@@ -340,15 +418,25 @@ const EventTriggerConfig: React.FC<EventTriggerConfigProps> = ({ data, onUpdate,
340
418
  >
341
419
  {events.length === 0 ? (
342
420
  'Выберите события, которые должен совершить пользователь для продолжения кампании.'
343
- ) : logic === 'or' ? (
344
- <>
345
- Кампания продолжится, когда пользователь выполнит{' '}
346
- <strong>любое</strong> из выбранных событий.
347
- </>
348
421
  ) : (
349
422
  <>
350
- Кампания продолжится только когда пользователь выполнит{' '}
351
- <strong>все {events.length}</strong> выбранных события.
423
+ {logic === 'or' ? (
424
+ <>
425
+ Кампания продолжится, когда пользователь выполнит{' '}
426
+ <strong>любое</strong> из выбранных событий
427
+ </>
428
+ ) : (
429
+ <>
430
+ Кампания продолжится только когда пользователь выполнит{' '}
431
+ <strong>все {events.length}</strong> выбранных события
432
+ </>
433
+ )}
434
+ {timeout > 0 && (
435
+ <>
436
+ {' '}или через <strong>{timeout} {timeoutUnit}</strong>
437
+ </>
438
+ )}
439
+ .
352
440
  </>
353
441
  )}
354
442
  </Typography>
@@ -336,7 +336,7 @@ const NodeEditPanel: React.FC<ExtendedNodeEditPanelProps> = ({
336
336
  <TextInput
337
337
  value={name}
338
338
  onChange={handleNameChange}
339
- disabled={disabled || stepType === 'entry'}
339
+ disabled={disabled}
340
340
  placeholder="Enter step name..."
341
341
  />
342
342
  </div>
@@ -61,7 +61,6 @@ const WaitConfig: React.FC<WaitConfigProps> = ({ data, onUpdate, disabled }) =>
61
61
  onValueChange={handleDurationChange}
62
62
  disabled={disabled}
63
63
  step={1}
64
- label=""
65
64
  />
66
65
  </div>
67
66
  </Box>
@@ -81,9 +80,8 @@ const WaitConfig: React.FC<WaitConfigProps> = ({ data, onUpdate, disabled }) =>
81
80
  <div style={{ width: '100%' }}>
82
81
  <SingleSelect
83
82
  value={durationUnit}
84
- onChange={handleUnitChange}
83
+ onChange={(val: string | number) => handleUnitChange(String(val))}
85
84
  disabled={disabled}
86
- label=""
87
85
  >
88
86
  {DURATION_UNIT_OPTIONS.map((opt) => (
89
87
  <SingleSelectOption key={opt.value} value={opt.value}>
@@ -8,6 +8,8 @@ export type EventTriggerLogic = 'and' | 'or';
8
8
  export interface EventTriggerConfig {
9
9
  events: string[];
10
10
  logic: EventTriggerLogic;
11
+ timeout?: number;
12
+ timeoutUnit?: DurationUnit;
11
13
  }
12
14
 
13
15
  export interface BranchSegmentRef {
@@ -226,7 +226,7 @@ interface StrapiTheme {
226
226
  }
227
227
 
228
228
  const useThemeColors = () => {
229
- const theme = useTheme() as StrapiTheme;
229
+ const theme = useTheme() as unknown as StrapiTheme;
230
230
  const isDark = theme?.colors?.neutral0 === '#212134';
231
231
 
232
232
  return useMemo(
@@ -524,7 +524,8 @@ const TriggerConfigField = forwardRef<HTMLDivElement, TriggerConfigFieldProps>(
524
524
  disabled={disabled}
525
525
  creatable
526
526
  createMessage={(v: string) => `Use custom event: "${v}"`}
527
- onCreateOption={(v: string) => {
527
+ onCreateOption={(v?: string) => {
528
+ if (!v) return;
528
529
  const eventValue = extractEventValue(v);
529
530
  handleUpdate({ eventName: eventValue });
530
531
  setInputText(getEventLabel(eventValue));
@@ -617,8 +618,8 @@ const TriggerConfigField = forwardRef<HTMLDivElement, TriggerConfigFieldProps>(
617
618
  <Box style={{ width: 120 }}>
618
619
  <SingleSelect
619
620
  value={config.delayUnit || 'minutes'}
620
- onChange={(val: string) =>
621
- handleUpdate({ delayUnit: val as TriggerConfig['delayUnit'] })
621
+ onChange={(val: string | number) =>
622
+ handleUpdate({ delayUnit: String(val) as TriggerConfig['delayUnit'] })
622
623
  }
623
624
  disabled={disabled}
624
625
  size="S"
@@ -743,8 +744,8 @@ const TriggerConfigField = forwardRef<HTMLDivElement, TriggerConfigFieldProps>(
743
744
  <Box style={{ width: 160 }}>
744
745
  <SingleSelect
745
746
  value={String(config.scheduleEveryMinutes ?? 5)}
746
- onChange={(val: string) =>
747
- handleUpdate({ scheduleEveryMinutes: parseInt(val, 10) || 5 })
747
+ onChange={(val: string | number) =>
748
+ handleUpdate({ scheduleEveryMinutes: parseInt(String(val), 10) || 5 })
748
749
  }
749
750
  disabled={disabled}
750
751
  size="S"
@@ -885,8 +886,8 @@ const TriggerConfigField = forwardRef<HTMLDivElement, TriggerConfigFieldProps>(
885
886
  textColor="neutral500"
886
887
  style={{ marginTop: 8, display: 'block' }}
887
888
  >
888
- Формат: минута час день месяц день_недели (напр., "0 12 * * *" = ежедневно в 12:00
889
- UTC)
889
+ Формат: минута час день месяц день_недели (напр., "0 12 * * *" = ежедневно в
890
+ 12:00 UTC)
890
891
  </Typography>
891
892
  )}
892
893
  </Box>
@@ -894,8 +895,8 @@ const TriggerConfigField = forwardRef<HTMLDivElement, TriggerConfigFieldProps>(
894
895
  </Box>
895
896
  )}
896
897
 
897
- {error && <Field.Error>{error}</Field.Error>}
898
- {hint && <Field.Hint>{hint}</Field.Hint>}
898
+ {error && <Field.Error />}
899
+ {hint && <Field.Hint />}
899
900
  </Flex>
900
901
  </Field.Root>
901
902
  );
@@ -0,0 +1,109 @@
1
+ export interface TriggerParamDef {
2
+ key: string;
3
+ label: string;
4
+ description: string;
5
+ unit: string;
6
+ min: number;
7
+ placeholder: string;
8
+ stageTypes: string[];
9
+ }
10
+
11
+ export const TRIGGER_PARAMS: TriggerParamDef[] = [
12
+ {
13
+ key: 'delaySeconds',
14
+ label: 'Задержка',
15
+ description: 'Задержка перед показом после срабатывания триггера',
16
+ unit: 'сек',
17
+ min: 0,
18
+ placeholder: '7',
19
+ stageTypes: ['side_hint', 'modal', 'retry'],
20
+ },
21
+ {
22
+ key: 'timeOnSiteSeconds',
23
+ label: 'Время на сайте',
24
+ description: 'Мин. время на сайте для срабатывания',
25
+ unit: 'сек',
26
+ min: 0,
27
+ placeholder: '120',
28
+ stageTypes: ['exit_intent', 'side_hint', 'modal'],
29
+ },
30
+ {
31
+ key: 'scrollThresholdPx',
32
+ label: 'Порог скролла',
33
+ description: 'Скролл в пикселях для срабатывания',
34
+ unit: 'px',
35
+ min: 0,
36
+ placeholder: '200',
37
+ stageTypes: ['side_hint'],
38
+ },
39
+ {
40
+ key: 'idleSeconds',
41
+ label: 'Бездействие',
42
+ description: 'Время без кликов/скролла',
43
+ unit: 'сек',
44
+ min: 0,
45
+ placeholder: '15',
46
+ stageTypes: ['side_hint'],
47
+ },
48
+ {
49
+ key: 'caseViewSeconds',
50
+ label: 'Просмотр кейса',
51
+ description: 'Время на странице кейса',
52
+ unit: 'сек',
53
+ min: 0,
54
+ placeholder: '12',
55
+ stageTypes: ['modal', 'side_hint'],
56
+ },
57
+ {
58
+ key: 'activePlayMinutes',
59
+ label: 'Активная игра',
60
+ description: 'Время активной игры',
61
+ unit: 'мин',
62
+ min: 0,
63
+ placeholder: '10',
64
+ stageTypes: ['modal'],
65
+ },
66
+ {
67
+ key: 'actionCountThreshold',
68
+ label: 'Порог действий',
69
+ description: 'Кол-во значимых действий + низкий баланс',
70
+ unit: '',
71
+ min: 0,
72
+ placeholder: '2',
73
+ stageTypes: ['modal'],
74
+ },
75
+ {
76
+ key: 'balanceThresholdMultiplier',
77
+ label: 'Множитель баланса',
78
+ description: 'Множитель мин. цены кейса для "низкого баланса"',
79
+ unit: '×',
80
+ min: 0,
81
+ placeholder: '1.5',
82
+ stageTypes: ['modal', 'side_hint'],
83
+ },
84
+ {
85
+ key: 'abandonedTimeoutMinutes',
86
+ label: 'Таймаут заброшенного депозита',
87
+ description: 'Через сколько минут депозит считается заброшенным',
88
+ unit: 'мин',
89
+ min: 1,
90
+ placeholder: '10',
91
+ stageTypes: ['retry'],
92
+ },
93
+ {
94
+ key: 'minDepositAmount',
95
+ label: 'Мин. сумма депозита',
96
+ description: 'Подстановка в {{min_deposit}} в текстах',
97
+ unit: '₽',
98
+ min: 0,
99
+ placeholder: '250',
100
+ stageTypes: ['modal', 'side_hint', 'retry'],
101
+ },
102
+ ];
103
+
104
+ export const STAGE_TYPE_LABELS: Record<string, string> = {
105
+ side_hint: 'Side-hint',
106
+ modal: 'Modal',
107
+ exit_intent: 'Exit intent',
108
+ retry: 'Retry',
109
+ };