@inspirer-dev/crm-dashboard 1.0.83 → 1.0.85

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.
@@ -10,16 +10,20 @@ import {
10
10
  Tooltip,
11
11
  Card,
12
12
  CardContent,
13
+ SingleSelect,
14
+ SingleSelectOption,
13
15
  } from '@strapi/design-system';
14
- import { Plus, Trash, ArrowUp, ArrowDown, Link as LinkIcon } from '@strapi/icons';
16
+ import { Plus, Trash, ArrowUp, ArrowDown, Link as LinkIcon, Layout } from '@strapi/icons';
15
17
  import { useTheme } from 'styled-components';
16
18
  import { generateId } from '../RulesBuilder/utils';
17
19
 
18
20
  type TelegramButton = {
19
21
  id: string;
20
22
  text: string;
21
- url: string;
23
+ url?: string;
22
24
  row?: number;
25
+ type?: 'url' | 'screen';
26
+ screenSlug?: string;
23
27
  };
24
28
 
25
29
  interface ButtonsBuilderProps {
@@ -37,26 +41,25 @@ interface ButtonsBuilderProps {
37
41
  hint?: string;
38
42
  }
39
43
 
44
+ const mapRawButton = (b: any): TelegramButton => ({
45
+ id: typeof b?.id === 'string' ? b.id : generateId(),
46
+ text: typeof b?.text === 'string' ? b.text : '',
47
+ url: typeof b?.url === 'string' ? b.url : '',
48
+ row: typeof b?.row === 'number' ? b.row : 0,
49
+ type: b?.type === 'screen' ? 'screen' : 'url',
50
+ screenSlug: typeof b?.screenSlug === 'string' ? b.screenSlug : '',
51
+ });
52
+
40
53
  const parseButtons = (value: string | TelegramButton[] | null | undefined): TelegramButton[] => {
41
54
  if (!value) return [];
42
55
  if (Array.isArray(value)) {
43
- return value.map((b) => ({
44
- id: typeof b?.id === 'string' ? b.id : generateId(),
45
- text: typeof b?.text === 'string' ? b.text : '',
46
- url: typeof b?.url === 'string' ? b.url : '',
47
- row: typeof b?.row === 'number' ? b.row : 0,
48
- }));
56
+ return value.map(mapRawButton);
49
57
  }
50
58
  if (typeof value === 'string') {
51
59
  try {
52
60
  const parsed = JSON.parse(value);
53
61
  if (!Array.isArray(parsed)) return [];
54
- return parsed.map((b) => ({
55
- id: typeof b?.id === 'string' ? b.id : generateId(),
56
- text: typeof b?.text === 'string' ? b.text : '',
57
- url: typeof b?.url === 'string' ? b.url : '',
58
- row: typeof b?.row === 'number' ? b.row : 0,
59
- }));
62
+ return parsed.map(mapRawButton);
60
63
  } catch {
61
64
  return [];
62
65
  }
@@ -103,35 +106,39 @@ const useThemeColors = () => {
103
106
  }), [isDark]);
104
107
  };
105
108
 
106
- const TelegramButtonPreview: React.FC<{ text: string; url: string }> = ({ text, url }) => (
107
- <a
108
- href={url || '#'}
109
- target="_blank"
110
- rel="noopener noreferrer"
111
- onClick={(e) => !url && e.preventDefault()}
112
- style={{
113
- display: 'inline-flex',
114
- alignItems: 'center',
115
- justifyContent: 'center',
116
- gap: '6px',
117
- padding: '8px 16px',
118
- backgroundColor: '#3390ec',
119
- color: '#ffffff',
120
- borderRadius: '8px',
121
- textDecoration: 'none',
122
- fontSize: '14px',
123
- fontWeight: 500,
124
- cursor: url ? 'pointer' : 'default',
125
- transition: 'background-color 0.15s ease',
126
- minWidth: '80px',
127
- }}
128
- onMouseEnter={(e) => url && (e.currentTarget.style.backgroundColor = '#2b7ed8')}
129
- onMouseLeave={(e) => (e.currentTarget.style.backgroundColor = '#3390ec')}
130
- >
131
- <LinkIcon width={14} height={14} style={{ opacity: 0.9 }} />
132
- <span>{text || '(empty)'}</span>
133
- </a>
134
- );
109
+ const TelegramButtonPreview: React.FC<{ text: string; btnType?: 'url' | 'screen' }> = ({ text, btnType }) => {
110
+ const isScreen = btnType === 'screen';
111
+ const bgColor = isScreen ? '#7c3aed' : '#3390ec';
112
+ const bgHover = isScreen ? '#6d28d9' : '#2b7ed8';
113
+ return (
114
+ <span
115
+ style={{
116
+ display: 'inline-flex',
117
+ alignItems: 'center',
118
+ justifyContent: 'center',
119
+ gap: '6px',
120
+ padding: '8px 16px',
121
+ backgroundColor: bgColor,
122
+ color: '#ffffff',
123
+ borderRadius: '8px',
124
+ textDecoration: 'none',
125
+ fontSize: '14px',
126
+ fontWeight: 500,
127
+ cursor: 'default',
128
+ transition: 'background-color 0.15s ease',
129
+ minWidth: '80px',
130
+ }}
131
+ onMouseEnter={(e) => (e.currentTarget.style.backgroundColor = bgHover)}
132
+ onMouseLeave={(e) => (e.currentTarget.style.backgroundColor = bgColor)}
133
+ >
134
+ {isScreen
135
+ ? <Layout width={14} height={14} style={{ opacity: 0.9 }} />
136
+ : <LinkIcon width={14} height={14} style={{ opacity: 0.9 }} />
137
+ }
138
+ <span>{text || '(empty)'}</span>
139
+ </span>
140
+ );
141
+ };
135
142
 
136
143
  const ButtonsBuilder = forwardRef<HTMLDivElement, ButtonsBuilderProps>(
137
144
  ({ name, value, onChange, intlLabel, disabled, error, required, hint }, ref) => {
@@ -150,7 +157,7 @@ const ButtonsBuilder = forwardRef<HTMLDivElement, ButtonsBuilderProps>(
150
157
  const addButton = () => {
151
158
  update([
152
159
  ...buttons,
153
- { id: generateId(), text: 'Button', url: 'https://cases.gg', row: 0 },
160
+ { id: generateId(), text: 'Button', url: 'https://cases.gg', row: 0, type: 'url', screenSlug: '' },
154
161
  ]);
155
162
  };
156
163
 
@@ -222,7 +229,8 @@ const ButtonsBuilder = forwardRef<HTMLDivElement, ButtonsBuilderProps>(
222
229
  ) : (
223
230
  <Flex direction="column" gap={2}>
224
231
  {buttons.map((btn, idx) => {
225
- const urlOk = btn.url.length === 0 ? true : isValidUrl(btn.url);
232
+ const isScreen = btn.type === 'screen';
233
+ const urlOk = isScreen || (btn.url ?? '').length === 0 || isValidUrl(btn.url ?? '');
226
234
  return (
227
235
  <Card
228
236
  key={btn.id}
@@ -251,31 +259,59 @@ const ButtonsBuilder = forwardRef<HTMLDivElement, ButtonsBuilderProps>(
251
259
  </Typography>
252
260
  </Box>
253
261
 
254
- <Box style={{ flex: 1, minWidth: 120 }}>
255
- <TextInput
256
- placeholder="Button text"
257
- value={btn.text}
258
- onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
259
- updateButton(btn.id, { text: e.target.value })
262
+ <Box style={{ width: 100, flexShrink: 0 }}>
263
+ <SingleSelect
264
+ value={btn.type || 'url'}
265
+ onChange={(val: string) =>
266
+ updateButton(btn.id, { type: val as 'url' | 'screen' })
260
267
  }
261
268
  disabled={disabled}
262
269
  size="S"
263
- />
270
+ >
271
+ <SingleSelectOption value="url">URL</SingleSelectOption>
272
+ <SingleSelectOption value="screen">Screen</SingleSelectOption>
273
+ </SingleSelect>
264
274
  </Box>
265
275
 
266
- <Box style={{ flex: 2, minWidth: 180 }}>
276
+ <Box style={{ flex: 1, minWidth: 120 }}>
267
277
  <TextInput
268
- placeholder="https://..."
269
- value={btn.url}
278
+ placeholder="Button text"
279
+ value={btn.text}
270
280
  onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
271
- updateButton(btn.id, { url: e.target.value })
281
+ updateButton(btn.id, { text: e.target.value })
272
282
  }
273
283
  disabled={disabled}
274
284
  size="S"
275
- hasError={!urlOk}
276
285
  />
277
286
  </Box>
278
287
 
288
+ {isScreen ? (
289
+ <Box style={{ flex: 2, minWidth: 180 }}>
290
+ <TextInput
291
+ placeholder="screen-slug"
292
+ value={btn.screenSlug ?? ''}
293
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
294
+ updateButton(btn.id, { screenSlug: e.target.value })
295
+ }
296
+ disabled={disabled}
297
+ size="S"
298
+ />
299
+ </Box>
300
+ ) : (
301
+ <Box style={{ flex: 2, minWidth: 180 }}>
302
+ <TextInput
303
+ placeholder="https://..."
304
+ value={btn.url ?? ''}
305
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
306
+ updateButton(btn.id, { url: e.target.value })
307
+ }
308
+ disabled={disabled}
309
+ size="S"
310
+ hasError={!urlOk}
311
+ />
312
+ </Box>
313
+ )}
314
+
279
315
  <Box style={{ width: 60 }}>
280
316
  <TextInput
281
317
  type="number"
@@ -359,7 +395,7 @@ const ButtonsBuilder = forwardRef<HTMLDivElement, ButtonsBuilderProps>(
359
395
  {previewRows.map((row) => (
360
396
  <Flex key={row.row} gap={2} wrap="wrap" justifyContent="center">
361
397
  {row.items.map((b) => (
362
- <TelegramButtonPreview key={b.id} text={b.text} url={b.url} />
398
+ <TelegramButtonPreview key={b.id} text={b.text} btnType={b.type} />
363
399
  ))}
364
400
  </Flex>
365
401
  ))}
@@ -1,4 +1,4 @@
1
- import React, { forwardRef, useEffect, useMemo, useState } from 'react';
1
+ import React, { forwardRef, useMemo, useRef, useState } from 'react';
2
2
  import {
3
3
  Box,
4
4
  Button,
@@ -17,7 +17,7 @@ import { Plus, Trash } from '@strapi/icons';
17
17
  import { useTheme } from 'styled-components';
18
18
  import { TRIGGER_PARAMS, STAGE_TYPE_LABELS, type TriggerParamDef } from './constants';
19
19
 
20
- type TriggerParamsValue = Record<string, number | undefined>;
20
+ type TriggerParamsValue = Record<string, number | null>;
21
21
 
22
22
  interface TriggerParamsFieldProps {
23
23
  name: string;
@@ -47,13 +47,11 @@ const parseValue = (value: string | TriggerParamsValue | null | undefined): Trig
47
47
  };
48
48
 
49
49
  const serialize = (params: TriggerParamsValue): string => {
50
- const clean: Record<string, number> = {};
50
+ const out: Record<string, number> = {};
51
51
  for (const [key, val] of Object.entries(params)) {
52
- if (val !== undefined && val !== null) {
53
- clean[key] = val;
54
- }
52
+ if (typeof val === 'number') out[key] = val;
55
53
  }
56
- return JSON.stringify(clean);
54
+ return JSON.stringify(out);
57
55
  };
58
56
 
59
57
  interface StrapiTheme {
@@ -77,13 +75,10 @@ const useThemeColors = () => {
77
75
 
78
76
  const TriggerParamsField = forwardRef<HTMLDivElement, TriggerParamsFieldProps>(
79
77
  ({ name, value, onChange, intlLabel, disabled, error, required, hint }, ref) => {
80
- const [params, setParams] = useState<TriggerParamsValue>(() => parseValue(value));
78
+ const initialRef = useRef(value);
79
+ const [params, setParams] = useState<TriggerParamsValue>(() => parseValue(initialRef.current));
81
80
  const colors = useThemeColors();
82
81
 
83
- useEffect(() => {
84
- setParams(parseValue(value));
85
- }, [value]);
86
-
87
82
  const update = (next: TriggerParamsValue) => {
88
83
  setParams(next);
89
84
  onChange({ target: { name, value: serialize(next) } });
@@ -96,7 +91,7 @@ const TriggerParamsField = forwardRef<HTMLDivElement, TriggerParamsFieldProps>(
96
91
  const addParam = (key: string) => {
97
92
  const def = TRIGGER_PARAMS.find((p) => p.key === key);
98
93
  if (!def) return;
99
- update({ ...params, [key]: undefined });
94
+ update({ ...params, [key]: null });
100
95
  };
101
96
 
102
97
  const removeParam = (key: string) => {
@@ -106,7 +101,7 @@ const TriggerParamsField = forwardRef<HTMLDivElement, TriggerParamsFieldProps>(
106
101
  };
107
102
 
108
103
  const setParamValue = (key: string, val: number | undefined) => {
109
- update({ ...params, [key]: val });
104
+ update({ ...params, [key]: val ?? null });
110
105
  };
111
106
 
112
107
  const getParamDef = (key: string): TriggerParamDef | undefined =>
@@ -34,8 +34,10 @@ export type RulesConfig = RuleGroup;
34
34
 
35
35
  export interface TelegramButton {
36
36
  text: string;
37
- url: string;
37
+ url?: string;
38
38
  row?: number;
39
+ type?: 'url' | 'screen';
40
+ screenSlug?: string;
39
41
  }
40
42
 
41
43
  export interface CrmTemplate {
@@ -116,13 +116,11 @@ const parseValue = (value) => {
116
116
  return {};
117
117
  };
118
118
  const serialize = (params) => {
119
- const clean = {};
119
+ const out = {};
120
120
  for (const [key, val] of Object.entries(params)) {
121
- if (val !== void 0 && val !== null) {
122
- clean[key] = val;
123
- }
121
+ if (typeof val === "number") out[key] = val;
124
122
  }
125
- return JSON.stringify(clean);
123
+ return JSON.stringify(out);
126
124
  };
127
125
  const useThemeColors = () => {
128
126
  const theme = styledComponents.useTheme();
@@ -139,11 +137,9 @@ const useThemeColors = () => {
139
137
  };
140
138
  const TriggerParamsField = React.forwardRef(
141
139
  ({ name, value, onChange, intlLabel, disabled, error, required, hint }, ref) => {
142
- const [params, setParams] = React.useState(() => parseValue(value));
140
+ const initialRef = React.useRef(value);
141
+ const [params, setParams] = React.useState(() => parseValue(initialRef.current));
143
142
  const colors = useThemeColors();
144
- React.useEffect(() => {
145
- setParams(parseValue(value));
146
- }, [value]);
147
143
  const update = (next) => {
148
144
  setParams(next);
149
145
  onChange({ target: { name, value: serialize(next) } });
@@ -153,7 +149,7 @@ const TriggerParamsField = React.forwardRef(
153
149
  const addParam = (key) => {
154
150
  const def = TRIGGER_PARAMS.find((p) => p.key === key);
155
151
  if (!def) return;
156
- update({ ...params, [key]: void 0 });
152
+ update({ ...params, [key]: null });
157
153
  };
158
154
  const removeParam = (key) => {
159
155
  const next = { ...params };
@@ -161,7 +157,7 @@ const TriggerParamsField = React.forwardRef(
161
157
  update(next);
162
158
  };
163
159
  const setParamValue = (key, val) => {
164
- update({ ...params, [key]: val });
160
+ update({ ...params, [key]: val ?? null });
165
161
  };
166
162
  const getParamDef = (key) => TRIGGER_PARAMS.find((p) => p.key === key);
167
163
  return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Root, { name, error, required, hint, ref, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 3, children: [
@@ -1,29 +1,27 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { forwardRef, useState, useEffect, useMemo } from "react";
3
- import { Field, Flex, Box, Typography, Button, Card, CardContent, TextInput, Tooltip, IconButton } from "@strapi/design-system";
4
- import { Plus, ArrowUp, ArrowDown, Trash, Link } from "@strapi/icons";
3
+ import { Field, Flex, Box, Typography, Button, Card, CardContent, SingleSelect, SingleSelectOption, TextInput, Tooltip, IconButton } from "@strapi/design-system";
4
+ import { Plus, ArrowUp, ArrowDown, Trash, Layout, Link } from "@strapi/icons";
5
5
  import { useTheme } from "styled-components";
6
6
  import { m as generateId } from "./utils-BHneBJ7U.mjs";
7
+ const mapRawButton = (b) => ({
8
+ id: typeof b?.id === "string" ? b.id : generateId(),
9
+ text: typeof b?.text === "string" ? b.text : "",
10
+ url: typeof b?.url === "string" ? b.url : "",
11
+ row: typeof b?.row === "number" ? b.row : 0,
12
+ type: b?.type === "screen" ? "screen" : "url",
13
+ screenSlug: typeof b?.screenSlug === "string" ? b.screenSlug : ""
14
+ });
7
15
  const parseButtons = (value) => {
8
16
  if (!value) return [];
9
17
  if (Array.isArray(value)) {
10
- return value.map((b) => ({
11
- id: typeof b?.id === "string" ? b.id : generateId(),
12
- text: typeof b?.text === "string" ? b.text : "",
13
- url: typeof b?.url === "string" ? b.url : "",
14
- row: typeof b?.row === "number" ? b.row : 0
15
- }));
18
+ return value.map(mapRawButton);
16
19
  }
17
20
  if (typeof value === "string") {
18
21
  try {
19
22
  const parsed = JSON.parse(value);
20
23
  if (!Array.isArray(parsed)) return [];
21
- return parsed.map((b) => ({
22
- id: typeof b?.id === "string" ? b.id : generateId(),
23
- text: typeof b?.text === "string" ? b.text : "",
24
- url: typeof b?.url === "string" ? b.url : "",
25
- row: typeof b?.row === "number" ? b.row : 0
26
- }));
24
+ return parsed.map(mapRawButton);
27
25
  } catch {
28
26
  return [];
29
27
  }
@@ -59,37 +57,38 @@ const useThemeColors = () => {
59
57
  }
60
58
  }), [isDark]);
61
59
  };
62
- const TelegramButtonPreview = ({ text, url }) => /* @__PURE__ */ jsxs(
63
- "a",
64
- {
65
- href: url || "#",
66
- target: "_blank",
67
- rel: "noopener noreferrer",
68
- onClick: (e) => !url && e.preventDefault(),
69
- style: {
70
- display: "inline-flex",
71
- alignItems: "center",
72
- justifyContent: "center",
73
- gap: "6px",
74
- padding: "8px 16px",
75
- backgroundColor: "#3390ec",
76
- color: "#ffffff",
77
- borderRadius: "8px",
78
- textDecoration: "none",
79
- fontSize: "14px",
80
- fontWeight: 500,
81
- cursor: url ? "pointer" : "default",
82
- transition: "background-color 0.15s ease",
83
- minWidth: "80px"
84
- },
85
- onMouseEnter: (e) => url && (e.currentTarget.style.backgroundColor = "#2b7ed8"),
86
- onMouseLeave: (e) => e.currentTarget.style.backgroundColor = "#3390ec",
87
- children: [
88
- /* @__PURE__ */ jsx(Link, { width: 14, height: 14, style: { opacity: 0.9 } }),
89
- /* @__PURE__ */ jsx("span", { children: text || "(empty)" })
90
- ]
91
- }
92
- );
60
+ const TelegramButtonPreview = ({ text, btnType }) => {
61
+ const isScreen = btnType === "screen";
62
+ const bgColor = isScreen ? "#7c3aed" : "#3390ec";
63
+ const bgHover = isScreen ? "#6d28d9" : "#2b7ed8";
64
+ return /* @__PURE__ */ jsxs(
65
+ "span",
66
+ {
67
+ style: {
68
+ display: "inline-flex",
69
+ alignItems: "center",
70
+ justifyContent: "center",
71
+ gap: "6px",
72
+ padding: "8px 16px",
73
+ backgroundColor: bgColor,
74
+ color: "#ffffff",
75
+ borderRadius: "8px",
76
+ textDecoration: "none",
77
+ fontSize: "14px",
78
+ fontWeight: 500,
79
+ cursor: "default",
80
+ transition: "background-color 0.15s ease",
81
+ minWidth: "80px"
82
+ },
83
+ onMouseEnter: (e) => e.currentTarget.style.backgroundColor = bgHover,
84
+ onMouseLeave: (e) => e.currentTarget.style.backgroundColor = bgColor,
85
+ children: [
86
+ isScreen ? /* @__PURE__ */ jsx(Layout, { width: 14, height: 14, style: { opacity: 0.9 } }) : /* @__PURE__ */ jsx(Link, { width: 14, height: 14, style: { opacity: 0.9 } }),
87
+ /* @__PURE__ */ jsx("span", { children: text || "(empty)" })
88
+ ]
89
+ }
90
+ );
91
+ };
93
92
  const ButtonsBuilder = forwardRef(
94
93
  ({ name, value, onChange, intlLabel, disabled, error, required, hint }, ref) => {
95
94
  const [buttons, setButtons] = useState(() => parseButtons(value));
@@ -104,7 +103,7 @@ const ButtonsBuilder = forwardRef(
104
103
  const addButton = () => {
105
104
  update([
106
105
  ...buttons,
107
- { id: generateId(), text: "Button", url: "https://cases.gg", row: 0 }
106
+ { id: generateId(), text: "Button", url: "https://cases.gg", row: 0, type: "url", screenSlug: "" }
108
107
  ]);
109
108
  };
110
109
  const updateButton = (id, patch) => {
@@ -162,7 +161,8 @@ const ButtonsBuilder = forwardRef(
162
161
  ]
163
162
  }
164
163
  ) : /* @__PURE__ */ jsx(Flex, { direction: "column", gap: 2, children: buttons.map((btn, idx) => {
165
- const urlOk = btn.url.length === 0 ? true : isValidUrl(btn.url);
164
+ const isScreen = btn.type === "screen";
165
+ const urlOk = isScreen || (btn.url ?? "").length === 0 || isValidUrl(btn.url ?? "");
166
166
  return /* @__PURE__ */ jsx(
167
167
  Card,
168
168
  {
@@ -189,6 +189,19 @@ const ButtonsBuilder = forwardRef(
189
189
  children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "primary600", children: idx + 1 })
190
190
  }
191
191
  ),
192
+ /* @__PURE__ */ jsx(Box, { style: { width: 100, flexShrink: 0 }, children: /* @__PURE__ */ jsxs(
193
+ SingleSelect,
194
+ {
195
+ value: btn.type || "url",
196
+ onChange: (val) => updateButton(btn.id, { type: val }),
197
+ disabled,
198
+ size: "S",
199
+ children: [
200
+ /* @__PURE__ */ jsx(SingleSelectOption, { value: "url", children: "URL" }),
201
+ /* @__PURE__ */ jsx(SingleSelectOption, { value: "screen", children: "Screen" })
202
+ ]
203
+ }
204
+ ) }),
192
205
  /* @__PURE__ */ jsx(Box, { style: { flex: 1, minWidth: 120 }, children: /* @__PURE__ */ jsx(
193
206
  TextInput,
194
207
  {
@@ -199,11 +212,20 @@ const ButtonsBuilder = forwardRef(
199
212
  size: "S"
200
213
  }
201
214
  ) }),
202
- /* @__PURE__ */ jsx(Box, { style: { flex: 2, minWidth: 180 }, children: /* @__PURE__ */ jsx(
215
+ isScreen ? /* @__PURE__ */ jsx(Box, { style: { flex: 2, minWidth: 180 }, children: /* @__PURE__ */ jsx(
216
+ TextInput,
217
+ {
218
+ placeholder: "screen-slug",
219
+ value: btn.screenSlug ?? "",
220
+ onChange: (e) => updateButton(btn.id, { screenSlug: e.target.value }),
221
+ disabled,
222
+ size: "S"
223
+ }
224
+ ) }) : /* @__PURE__ */ jsx(Box, { style: { flex: 2, minWidth: 180 }, children: /* @__PURE__ */ jsx(
203
225
  TextInput,
204
226
  {
205
227
  placeholder: "https://...",
206
- value: btn.url,
228
+ value: btn.url ?? "",
207
229
  onChange: (e) => updateButton(btn.id, { url: e.target.value }),
208
230
  disabled,
209
231
  size: "S",
@@ -282,7 +304,7 @@ const ButtonsBuilder = forwardRef(
282
304
  children: "PREVIEW"
283
305
  }
284
306
  ),
285
- /* @__PURE__ */ jsx(Flex, { direction: "column", gap: 2, alignItems: "center", children: previewRows.map((row) => /* @__PURE__ */ jsx(Flex, { gap: 2, wrap: "wrap", justifyContent: "center", children: row.items.map((b) => /* @__PURE__ */ jsx(TelegramButtonPreview, { text: b.text, url: b.url }, b.id)) }, row.row)) })
307
+ /* @__PURE__ */ jsx(Flex, { direction: "column", gap: 2, alignItems: "center", children: previewRows.map((row) => /* @__PURE__ */ jsx(Flex, { gap: 2, wrap: "wrap", justifyContent: "center", children: row.items.map((b) => /* @__PURE__ */ jsx(TelegramButtonPreview, { text: b.text, btnType: b.type }, b.id)) }, row.row)) })
286
308
  ]
287
309
  }
288
310
  ),
@@ -1,5 +1,5 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
- import { forwardRef, useState, useEffect, useMemo } from "react";
2
+ import { forwardRef, useRef, useState, useMemo } from "react";
3
3
  import { Field, Flex, Box, Typography, Card, CardContent, NumberInput, Tooltip, IconButton, SingleSelect, SingleSelectOption, Button } from "@strapi/design-system";
4
4
  import { Trash, Plus } from "@strapi/icons";
5
5
  import { useTheme } from "styled-components";
@@ -114,13 +114,11 @@ const parseValue = (value) => {
114
114
  return {};
115
115
  };
116
116
  const serialize = (params) => {
117
- const clean = {};
117
+ const out = {};
118
118
  for (const [key, val] of Object.entries(params)) {
119
- if (val !== void 0 && val !== null) {
120
- clean[key] = val;
121
- }
119
+ if (typeof val === "number") out[key] = val;
122
120
  }
123
- return JSON.stringify(clean);
121
+ return JSON.stringify(out);
124
122
  };
125
123
  const useThemeColors = () => {
126
124
  const theme = useTheme();
@@ -137,11 +135,9 @@ const useThemeColors = () => {
137
135
  };
138
136
  const TriggerParamsField = forwardRef(
139
137
  ({ name, value, onChange, intlLabel, disabled, error, required, hint }, ref) => {
140
- const [params, setParams] = useState(() => parseValue(value));
138
+ const initialRef = useRef(value);
139
+ const [params, setParams] = useState(() => parseValue(initialRef.current));
141
140
  const colors = useThemeColors();
142
- useEffect(() => {
143
- setParams(parseValue(value));
144
- }, [value]);
145
141
  const update = (next) => {
146
142
  setParams(next);
147
143
  onChange({ target: { name, value: serialize(next) } });
@@ -151,7 +147,7 @@ const TriggerParamsField = forwardRef(
151
147
  const addParam = (key) => {
152
148
  const def = TRIGGER_PARAMS.find((p) => p.key === key);
153
149
  if (!def) return;
154
- update({ ...params, [key]: void 0 });
150
+ update({ ...params, [key]: null });
155
151
  };
156
152
  const removeParam = (key) => {
157
153
  const next = { ...params };
@@ -159,7 +155,7 @@ const TriggerParamsField = forwardRef(
159
155
  update(next);
160
156
  };
161
157
  const setParamValue = (key, val) => {
162
- update({ ...params, [key]: val });
158
+ update({ ...params, [key]: val ?? null });
163
159
  };
164
160
  const getParamDef = (key) => TRIGGER_PARAMS.find((p) => p.key === key);
165
161
  return /* @__PURE__ */ jsx(Field.Root, { name, error, required, hint, ref, children: /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 3, children: [
@@ -6,26 +6,24 @@ const designSystem = require("@strapi/design-system");
6
6
  const icons = require("@strapi/icons");
7
7
  const styledComponents = require("styled-components");
8
8
  const utils = require("./utils-CAs_GSGv.js");
9
+ const mapRawButton = (b) => ({
10
+ id: typeof b?.id === "string" ? b.id : utils.generateId(),
11
+ text: typeof b?.text === "string" ? b.text : "",
12
+ url: typeof b?.url === "string" ? b.url : "",
13
+ row: typeof b?.row === "number" ? b.row : 0,
14
+ type: b?.type === "screen" ? "screen" : "url",
15
+ screenSlug: typeof b?.screenSlug === "string" ? b.screenSlug : ""
16
+ });
9
17
  const parseButtons = (value) => {
10
18
  if (!value) return [];
11
19
  if (Array.isArray(value)) {
12
- return value.map((b) => ({
13
- id: typeof b?.id === "string" ? b.id : utils.generateId(),
14
- text: typeof b?.text === "string" ? b.text : "",
15
- url: typeof b?.url === "string" ? b.url : "",
16
- row: typeof b?.row === "number" ? b.row : 0
17
- }));
20
+ return value.map(mapRawButton);
18
21
  }
19
22
  if (typeof value === "string") {
20
23
  try {
21
24
  const parsed = JSON.parse(value);
22
25
  if (!Array.isArray(parsed)) return [];
23
- return parsed.map((b) => ({
24
- id: typeof b?.id === "string" ? b.id : utils.generateId(),
25
- text: typeof b?.text === "string" ? b.text : "",
26
- url: typeof b?.url === "string" ? b.url : "",
27
- row: typeof b?.row === "number" ? b.row : 0
28
- }));
26
+ return parsed.map(mapRawButton);
29
27
  } catch {
30
28
  return [];
31
29
  }
@@ -61,37 +59,38 @@ const useThemeColors = () => {
61
59
  }
62
60
  }), [isDark]);
63
61
  };
64
- const TelegramButtonPreview = ({ text, url }) => /* @__PURE__ */ jsxRuntime.jsxs(
65
- "a",
66
- {
67
- href: url || "#",
68
- target: "_blank",
69
- rel: "noopener noreferrer",
70
- onClick: (e) => !url && e.preventDefault(),
71
- style: {
72
- display: "inline-flex",
73
- alignItems: "center",
74
- justifyContent: "center",
75
- gap: "6px",
76
- padding: "8px 16px",
77
- backgroundColor: "#3390ec",
78
- color: "#ffffff",
79
- borderRadius: "8px",
80
- textDecoration: "none",
81
- fontSize: "14px",
82
- fontWeight: 500,
83
- cursor: url ? "pointer" : "default",
84
- transition: "background-color 0.15s ease",
85
- minWidth: "80px"
86
- },
87
- onMouseEnter: (e) => url && (e.currentTarget.style.backgroundColor = "#2b7ed8"),
88
- onMouseLeave: (e) => e.currentTarget.style.backgroundColor = "#3390ec",
89
- children: [
90
- /* @__PURE__ */ jsxRuntime.jsx(icons.Link, { width: 14, height: 14, style: { opacity: 0.9 } }),
91
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: text || "(empty)" })
92
- ]
93
- }
94
- );
62
+ const TelegramButtonPreview = ({ text, btnType }) => {
63
+ const isScreen = btnType === "screen";
64
+ const bgColor = isScreen ? "#7c3aed" : "#3390ec";
65
+ const bgHover = isScreen ? "#6d28d9" : "#2b7ed8";
66
+ return /* @__PURE__ */ jsxRuntime.jsxs(
67
+ "span",
68
+ {
69
+ style: {
70
+ display: "inline-flex",
71
+ alignItems: "center",
72
+ justifyContent: "center",
73
+ gap: "6px",
74
+ padding: "8px 16px",
75
+ backgroundColor: bgColor,
76
+ color: "#ffffff",
77
+ borderRadius: "8px",
78
+ textDecoration: "none",
79
+ fontSize: "14px",
80
+ fontWeight: 500,
81
+ cursor: "default",
82
+ transition: "background-color 0.15s ease",
83
+ minWidth: "80px"
84
+ },
85
+ onMouseEnter: (e) => e.currentTarget.style.backgroundColor = bgHover,
86
+ onMouseLeave: (e) => e.currentTarget.style.backgroundColor = bgColor,
87
+ children: [
88
+ isScreen ? /* @__PURE__ */ jsxRuntime.jsx(icons.Layout, { width: 14, height: 14, style: { opacity: 0.9 } }) : /* @__PURE__ */ jsxRuntime.jsx(icons.Link, { width: 14, height: 14, style: { opacity: 0.9 } }),
89
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: text || "(empty)" })
90
+ ]
91
+ }
92
+ );
93
+ };
95
94
  const ButtonsBuilder = React.forwardRef(
96
95
  ({ name, value, onChange, intlLabel, disabled, error, required, hint }, ref) => {
97
96
  const [buttons, setButtons] = React.useState(() => parseButtons(value));
@@ -106,7 +105,7 @@ const ButtonsBuilder = React.forwardRef(
106
105
  const addButton = () => {
107
106
  update([
108
107
  ...buttons,
109
- { id: utils.generateId(), text: "Button", url: "https://cases.gg", row: 0 }
108
+ { id: utils.generateId(), text: "Button", url: "https://cases.gg", row: 0, type: "url", screenSlug: "" }
110
109
  ]);
111
110
  };
112
111
  const updateButton = (id, patch) => {
@@ -164,7 +163,8 @@ const ButtonsBuilder = React.forwardRef(
164
163
  ]
165
164
  }
166
165
  ) : /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { direction: "column", gap: 2, children: buttons.map((btn, idx) => {
167
- const urlOk = btn.url.length === 0 ? true : isValidUrl(btn.url);
166
+ const isScreen = btn.type === "screen";
167
+ const urlOk = isScreen || (btn.url ?? "").length === 0 || isValidUrl(btn.url ?? "");
168
168
  return /* @__PURE__ */ jsxRuntime.jsx(
169
169
  designSystem.Card,
170
170
  {
@@ -191,6 +191,19 @@ const ButtonsBuilder = React.forwardRef(
191
191
  children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", textColor: "primary600", children: idx + 1 })
192
192
  }
193
193
  ),
194
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { width: 100, flexShrink: 0 }, children: /* @__PURE__ */ jsxRuntime.jsxs(
195
+ designSystem.SingleSelect,
196
+ {
197
+ value: btn.type || "url",
198
+ onChange: (val) => updateButton(btn.id, { type: val }),
199
+ disabled,
200
+ size: "S",
201
+ children: [
202
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "url", children: "URL" }),
203
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "screen", children: "Screen" })
204
+ ]
205
+ }
206
+ ) }),
194
207
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { flex: 1, minWidth: 120 }, children: /* @__PURE__ */ jsxRuntime.jsx(
195
208
  designSystem.TextInput,
196
209
  {
@@ -201,11 +214,20 @@ const ButtonsBuilder = React.forwardRef(
201
214
  size: "S"
202
215
  }
203
216
  ) }),
204
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { flex: 2, minWidth: 180 }, children: /* @__PURE__ */ jsxRuntime.jsx(
217
+ isScreen ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { flex: 2, minWidth: 180 }, children: /* @__PURE__ */ jsxRuntime.jsx(
218
+ designSystem.TextInput,
219
+ {
220
+ placeholder: "screen-slug",
221
+ value: btn.screenSlug ?? "",
222
+ onChange: (e) => updateButton(btn.id, { screenSlug: e.target.value }),
223
+ disabled,
224
+ size: "S"
225
+ }
226
+ ) }) : /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { flex: 2, minWidth: 180 }, children: /* @__PURE__ */ jsxRuntime.jsx(
205
227
  designSystem.TextInput,
206
228
  {
207
229
  placeholder: "https://...",
208
- value: btn.url,
230
+ value: btn.url ?? "",
209
231
  onChange: (e) => updateButton(btn.id, { url: e.target.value }),
210
232
  disabled,
211
233
  size: "S",
@@ -284,7 +306,7 @@ const ButtonsBuilder = React.forwardRef(
284
306
  children: "PREVIEW"
285
307
  }
286
308
  ),
287
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { direction: "column", gap: 2, alignItems: "center", children: previewRows.map((row) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { gap: 2, wrap: "wrap", justifyContent: "center", children: row.items.map((b) => /* @__PURE__ */ jsxRuntime.jsx(TelegramButtonPreview, { text: b.text, url: b.url }, b.id)) }, row.row)) })
309
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { direction: "column", gap: 2, alignItems: "center", children: previewRows.map((row) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { gap: 2, wrap: "wrap", justifyContent: "center", children: row.items.map((b) => /* @__PURE__ */ jsxRuntime.jsx(TelegramButtonPreview, { text: b.text, btnType: b.type }, b.id)) }, row.row)) })
288
310
  ]
289
311
  }
290
312
  ),
@@ -107,7 +107,7 @@ const index = {
107
107
  components: {
108
108
  Input: async () => Promise.resolve().then(() => require(
109
109
  /* webpackChunkName: "crm-telegram-buttons" */
110
- "../_chunks/index-Cg0G_bTE.js"
110
+ "../_chunks/index-u-jR-zfs.js"
111
111
  ))
112
112
  },
113
113
  options: {
@@ -155,7 +155,7 @@ const index = {
155
155
  components: {
156
156
  Input: async () => Promise.resolve().then(() => require(
157
157
  /* webpackChunkName: "crm-trigger-params" */
158
- "../_chunks/index-2rErXqfH.js"
158
+ "../_chunks/index-CqRPcaXV.js"
159
159
  ))
160
160
  },
161
161
  options: {
@@ -106,7 +106,7 @@ const index = {
106
106
  components: {
107
107
  Input: async () => import(
108
108
  /* webpackChunkName: "crm-telegram-buttons" */
109
- "../_chunks/index-BKfFI_Jo.mjs"
109
+ "../_chunks/index-DRhcN7TG.mjs"
110
110
  )
111
111
  },
112
112
  options: {
@@ -154,7 +154,7 @@ const index = {
154
154
  components: {
155
155
  Input: async () => import(
156
156
  /* webpackChunkName: "crm-trigger-params" */
157
- "../_chunks/index-BMvCnlEy.mjs"
157
+ "../_chunks/index-DnyoVNLo.mjs"
158
158
  )
159
159
  },
160
160
  options: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inspirer-dev/crm-dashboard",
3
- "version": "1.0.83",
3
+ "version": "1.0.85",
4
4
  "description": "CRM Dashboard and Tools",
5
5
  "strapi": {
6
6
  "name": "crm-dashboard",