@inspirer-dev/crm-dashboard 1.0.9 → 1.0.11
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.
- package/admin/src/components/CancelConditionsField/constants.ts +104 -0
- package/admin/src/components/CancelConditionsField/index.tsx +381 -0
- package/admin/src/components/TriggerConfigField/index.tsx +333 -0
- package/admin/src/index.ts +51 -2
- package/admin/src/pages/HomePage/index.tsx +5 -5
- package/dist/_chunks/index-Bnjm_sYk.mjs +339 -0
- package/dist/_chunks/{index-CYXNVioH.js → index-C1dJdMYf.js} +96 -117
- package/dist/_chunks/index-Cf8DZYT6.js +341 -0
- package/dist/_chunks/{index-BWojad6n.mjs → index-DEONgZRM.mjs} +5 -5
- package/dist/_chunks/index-DFqEb9sm.mjs +253 -0
- package/dist/_chunks/index-DRJ5o0cz.js +253 -0
- package/dist/admin/index.js +49 -2
- package/dist/admin/index.mjs +50 -3
- package/dist/server/index.js +10 -0
- package/dist/server/index.mjs +10 -0
- package/package.json +1 -1
- package/server/src/register.ts +12 -0
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const jsxRuntime = require("react/jsx-runtime");
|
|
4
|
+
const React = require("react");
|
|
5
|
+
const designSystem = require("@strapi/design-system");
|
|
6
|
+
const icons = require("@strapi/icons");
|
|
7
|
+
const styled = require("styled-components");
|
|
8
|
+
const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
|
|
9
|
+
const React__default = /* @__PURE__ */ _interopDefault(React);
|
|
10
|
+
const booleanOperators = ["$eq", "$ne"];
|
|
11
|
+
const numberOperators = ["$eq", "$ne", "$gt", "$lt", "$gte", "$lte"];
|
|
12
|
+
const timeAgoOperators = ["$gt", "$lt", "$gte", "$lte"];
|
|
13
|
+
const CANCEL_METRICS = [
|
|
14
|
+
{
|
|
15
|
+
key: "has_deposit_since_trigger",
|
|
16
|
+
label: "Made Deposit",
|
|
17
|
+
valueType: "boolean",
|
|
18
|
+
description: "User made a successful deposit after the trigger event",
|
|
19
|
+
operators: booleanOperators
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
key: "has_activity_since_trigger",
|
|
23
|
+
label: "Had Activity",
|
|
24
|
+
valueType: "boolean",
|
|
25
|
+
description: "User had any activity (case open, upgrade, battle, etc.) after trigger",
|
|
26
|
+
operators: booleanOperators
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
key: "has_session_since_trigger",
|
|
30
|
+
label: "Returned to Site",
|
|
31
|
+
valueType: "boolean",
|
|
32
|
+
description: "User had a new session/visit after the trigger event",
|
|
33
|
+
operators: booleanOperators
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
key: "balance_sufficient",
|
|
37
|
+
label: "Balance Sufficient",
|
|
38
|
+
valueType: "boolean",
|
|
39
|
+
description: "User balance is now sufficient for the intended action",
|
|
40
|
+
operators: booleanOperators
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
key: "is_authenticated",
|
|
44
|
+
label: "Is Authenticated",
|
|
45
|
+
valueType: "boolean",
|
|
46
|
+
description: "User has logged in / authenticated",
|
|
47
|
+
operators: booleanOperators
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
key: "battle_completed",
|
|
51
|
+
label: "Battle Completed",
|
|
52
|
+
valueType: "boolean",
|
|
53
|
+
description: "The battle was completed or continued",
|
|
54
|
+
operators: booleanOperators
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
key: "upgrade_completed",
|
|
58
|
+
label: "Upgrade Completed",
|
|
59
|
+
valueType: "boolean",
|
|
60
|
+
description: "The upgrade was completed or retried",
|
|
61
|
+
operators: booleanOperators
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
key: "contract_completed",
|
|
65
|
+
label: "Contract Completed",
|
|
66
|
+
valueType: "boolean",
|
|
67
|
+
description: "The contract was completed",
|
|
68
|
+
operators: booleanOperators
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
key: "deposit_count",
|
|
72
|
+
label: "Deposit Count",
|
|
73
|
+
valueType: "number",
|
|
74
|
+
description: "Total number of deposits (e.g., for checking if 2nd deposit exists)",
|
|
75
|
+
operators: numberOperators
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
key: "case_open_count_since_trigger",
|
|
79
|
+
label: "Cases Opened Since Trigger",
|
|
80
|
+
valueType: "number",
|
|
81
|
+
description: "Number of cases opened after the trigger event",
|
|
82
|
+
operators: numberOperators
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
key: "time_since_trigger",
|
|
86
|
+
label: "Time Since Trigger",
|
|
87
|
+
valueType: "time_ago",
|
|
88
|
+
description: "How long since the trigger event fired",
|
|
89
|
+
operators: timeAgoOperators
|
|
90
|
+
}
|
|
91
|
+
];
|
|
92
|
+
const CANCEL_TIME_UNITS = [
|
|
93
|
+
{ value: "minutes_ago", label: "minutes" },
|
|
94
|
+
{ value: "hours_ago", label: "hours" }
|
|
95
|
+
];
|
|
96
|
+
const generateId = () => Math.random().toString(36).substring(2, 11);
|
|
97
|
+
const DEFAULT_CONFIG = {
|
|
98
|
+
logic: "$or",
|
|
99
|
+
rules: []
|
|
100
|
+
};
|
|
101
|
+
const parseConfig = (value) => {
|
|
102
|
+
if (!value) return DEFAULT_CONFIG;
|
|
103
|
+
try {
|
|
104
|
+
const parsed = JSON.parse(value);
|
|
105
|
+
if (parsed.logic && Array.isArray(parsed.rules)) {
|
|
106
|
+
return parsed;
|
|
107
|
+
}
|
|
108
|
+
return DEFAULT_CONFIG;
|
|
109
|
+
} catch {
|
|
110
|
+
return DEFAULT_CONFIG;
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
const serializeConfig = (config) => {
|
|
114
|
+
return JSON.stringify(config);
|
|
115
|
+
};
|
|
116
|
+
const getMetric = (key) => {
|
|
117
|
+
return CANCEL_METRICS.find((m) => m.key === key);
|
|
118
|
+
};
|
|
119
|
+
const OPERATORS = [
|
|
120
|
+
{ value: "$eq", label: "=" },
|
|
121
|
+
{ value: "$ne", label: "≠" },
|
|
122
|
+
{ value: "$gt", label: ">" },
|
|
123
|
+
{ value: "$lt", label: "<" },
|
|
124
|
+
{ value: "$gte", label: "≥" },
|
|
125
|
+
{ value: "$lte", label: "≤" }
|
|
126
|
+
];
|
|
127
|
+
const ValueInput = ({ metric, value, operator, onChange, disabled }) => {
|
|
128
|
+
if (metric.valueType === "boolean") {
|
|
129
|
+
const boolVal = value === true || value === "true";
|
|
130
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "center", children: [
|
|
131
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
132
|
+
designSystem.Switch,
|
|
133
|
+
{
|
|
134
|
+
label: "",
|
|
135
|
+
checked: boolVal,
|
|
136
|
+
onChange: () => onChange(!boolVal),
|
|
137
|
+
disabled
|
|
138
|
+
}
|
|
139
|
+
),
|
|
140
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", textColor: "neutral600", children: boolVal ? "true" : "false" })
|
|
141
|
+
] });
|
|
142
|
+
}
|
|
143
|
+
if (metric.valueType === "number") {
|
|
144
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
145
|
+
designSystem.TextInput,
|
|
146
|
+
{
|
|
147
|
+
type: "number",
|
|
148
|
+
value: String(value ?? 0),
|
|
149
|
+
onChange: (e) => onChange(parseInt(e.target.value, 10) || 0),
|
|
150
|
+
disabled,
|
|
151
|
+
"aria-label": "Value"
|
|
152
|
+
}
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
if (metric.valueType === "time_ago") {
|
|
156
|
+
const timeValue = value || { minutes_ago: 10 };
|
|
157
|
+
const unit = Object.keys(timeValue)[0] || "minutes_ago";
|
|
158
|
+
const numValue = timeValue[unit] || 10;
|
|
159
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, children: [
|
|
160
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { width: "80px" }, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
161
|
+
designSystem.TextInput,
|
|
162
|
+
{
|
|
163
|
+
type: "number",
|
|
164
|
+
value: String(numValue),
|
|
165
|
+
onChange: (e) => onChange({ [unit]: parseInt(e.target.value, 10) || 0 }),
|
|
166
|
+
disabled,
|
|
167
|
+
"aria-label": "Time value"
|
|
168
|
+
}
|
|
169
|
+
) }),
|
|
170
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { width: "120px" }, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
171
|
+
designSystem.SingleSelect,
|
|
172
|
+
{
|
|
173
|
+
value: unit,
|
|
174
|
+
onChange: (newUnit) => onChange({ [newUnit]: numValue }),
|
|
175
|
+
disabled,
|
|
176
|
+
children: CANCEL_TIME_UNITS.map((u) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: u.value, children: u.label }, u.value))
|
|
177
|
+
}
|
|
178
|
+
) })
|
|
179
|
+
] });
|
|
180
|
+
}
|
|
181
|
+
return null;
|
|
182
|
+
};
|
|
183
|
+
const RuleRow = ({ rule, onUpdate, onDelete, disabled }) => {
|
|
184
|
+
const theme = styled.useTheme();
|
|
185
|
+
const colors = theme?.colors;
|
|
186
|
+
const metric = getMetric(rule.field);
|
|
187
|
+
const availableOperators = metric?.operators || ["$eq"];
|
|
188
|
+
const handleFieldChange = (fieldKey) => {
|
|
189
|
+
const newMetric = getMetric(fieldKey);
|
|
190
|
+
let newValue = true;
|
|
191
|
+
if (newMetric?.valueType === "number") {
|
|
192
|
+
newValue = 0;
|
|
193
|
+
} else if (newMetric?.valueType === "time_ago") {
|
|
194
|
+
newValue = { minutes_ago: 10 };
|
|
195
|
+
}
|
|
196
|
+
const newOp = newMetric?.operators[0] || "$eq";
|
|
197
|
+
onUpdate({ ...rule, field: fieldKey, operator: newOp, value: newValue });
|
|
198
|
+
};
|
|
199
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
200
|
+
designSystem.Flex,
|
|
201
|
+
{
|
|
202
|
+
gap: 2,
|
|
203
|
+
alignItems: "center",
|
|
204
|
+
padding: 2,
|
|
205
|
+
background: "neutral0",
|
|
206
|
+
hasRadius: true,
|
|
207
|
+
style: { border: `1px solid ${colors?.neutral200 || "#dcdce4"}` },
|
|
208
|
+
children: [
|
|
209
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { flex: "1 1 200px", minWidth: "180px" }, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
210
|
+
designSystem.SingleSelect,
|
|
211
|
+
{
|
|
212
|
+
value: rule.field,
|
|
213
|
+
onChange: (val) => handleFieldChange(val),
|
|
214
|
+
disabled,
|
|
215
|
+
size: "S",
|
|
216
|
+
children: CANCEL_METRICS.map((m) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: m.key, children: m.label }, m.key))
|
|
217
|
+
}
|
|
218
|
+
) }),
|
|
219
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { width: "80px" }, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
220
|
+
designSystem.SingleSelect,
|
|
221
|
+
{
|
|
222
|
+
value: rule.operator,
|
|
223
|
+
onChange: (val) => onUpdate({ ...rule, operator: val }),
|
|
224
|
+
disabled,
|
|
225
|
+
size: "S",
|
|
226
|
+
children: OPERATORS.filter((op) => availableOperators.includes(op.value)).map((op) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: op.value, children: op.label }, op.value))
|
|
227
|
+
}
|
|
228
|
+
) }),
|
|
229
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { flex: "1 1 150px", minWidth: "120px" }, children: metric && /* @__PURE__ */ jsxRuntime.jsx(
|
|
230
|
+
ValueInput,
|
|
231
|
+
{
|
|
232
|
+
metric,
|
|
233
|
+
value: rule.value,
|
|
234
|
+
operator: rule.operator,
|
|
235
|
+
onChange: (val) => onUpdate({ ...rule, value: val }),
|
|
236
|
+
disabled
|
|
237
|
+
}
|
|
238
|
+
) }),
|
|
239
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Tooltip, { label: "Delete condition", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.IconButton, { onClick: onDelete, label: "Delete condition", variant: "ghost", disabled, children: /* @__PURE__ */ jsxRuntime.jsx(icons.Trash, {}) }) })
|
|
240
|
+
]
|
|
241
|
+
}
|
|
242
|
+
);
|
|
243
|
+
};
|
|
244
|
+
const CancelConditionsField = React.forwardRef(
|
|
245
|
+
({ name, value, onChange, intlLabel, disabled, error, required, hint }, ref) => {
|
|
246
|
+
const [config, setConfig] = React.useState(() => parseConfig(value));
|
|
247
|
+
const theme = styled.useTheme();
|
|
248
|
+
theme?.colors;
|
|
249
|
+
React__default.default.useEffect(() => {
|
|
250
|
+
const parsed = parseConfig(value);
|
|
251
|
+
setConfig(parsed);
|
|
252
|
+
}, [value]);
|
|
253
|
+
const handleUpdate = React.useCallback((newConfig) => {
|
|
254
|
+
setConfig(newConfig);
|
|
255
|
+
onChange({
|
|
256
|
+
target: {
|
|
257
|
+
name,
|
|
258
|
+
value: serializeConfig(newConfig)
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
}, [name, onChange]);
|
|
262
|
+
const handleAddRule = () => {
|
|
263
|
+
const newRule = {
|
|
264
|
+
id: generateId(),
|
|
265
|
+
field: "has_deposit_since_trigger",
|
|
266
|
+
operator: "$eq",
|
|
267
|
+
value: true
|
|
268
|
+
};
|
|
269
|
+
handleUpdate({
|
|
270
|
+
...config,
|
|
271
|
+
rules: [...config.rules, newRule]
|
|
272
|
+
});
|
|
273
|
+
};
|
|
274
|
+
const handleUpdateRule = (ruleId, updatedRule) => {
|
|
275
|
+
handleUpdate({
|
|
276
|
+
...config,
|
|
277
|
+
rules: config.rules.map((r) => r.id === ruleId ? updatedRule : r)
|
|
278
|
+
});
|
|
279
|
+
};
|
|
280
|
+
const handleDeleteRule = (ruleId) => {
|
|
281
|
+
handleUpdate({
|
|
282
|
+
...config,
|
|
283
|
+
rules: config.rules.filter((r) => r.id !== ruleId)
|
|
284
|
+
});
|
|
285
|
+
};
|
|
286
|
+
const handleLogicToggle = () => {
|
|
287
|
+
handleUpdate({
|
|
288
|
+
...config,
|
|
289
|
+
logic: config.logic === "$or" ? "$and" : "$or"
|
|
290
|
+
});
|
|
291
|
+
};
|
|
292
|
+
const hasRules = config.rules.length > 0;
|
|
293
|
+
return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Root, { name, error, required, hint, ref, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 3, children: [
|
|
294
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: intlLabel?.defaultMessage || "Cancel Conditions" }),
|
|
295
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: "Skip sending if any of these conditions are true (checked right before send)" }),
|
|
296
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Card, { background: "neutral100", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.CardContent, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { padding: 4, children: [
|
|
297
|
+
hasRules && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", marginBottom: 3, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "center", children: [
|
|
298
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
299
|
+
designSystem.Button,
|
|
300
|
+
{
|
|
301
|
+
variant: config.logic === "$or" ? "default" : "secondary",
|
|
302
|
+
size: "S",
|
|
303
|
+
onClick: handleLogicToggle,
|
|
304
|
+
disabled,
|
|
305
|
+
children: config.logic === "$or" ? "OR" : "AND"
|
|
306
|
+
}
|
|
307
|
+
),
|
|
308
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", textColor: "neutral600", children: config.logic === "$or" ? "Cancel if ANY condition is true" : "Cancel if ALL conditions are true" })
|
|
309
|
+
] }) }),
|
|
310
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 2, children: [
|
|
311
|
+
config.rules.map((rule) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
312
|
+
RuleRow,
|
|
313
|
+
{
|
|
314
|
+
rule,
|
|
315
|
+
onUpdate: (updated) => handleUpdateRule(rule.id, updated),
|
|
316
|
+
onDelete: () => handleDeleteRule(rule.id),
|
|
317
|
+
disabled
|
|
318
|
+
},
|
|
319
|
+
rule.id
|
|
320
|
+
)),
|
|
321
|
+
!hasRules && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 4, background: "neutral0", hasRadius: true, style: { textAlign: "center" }, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", textColor: "neutral500", children: "No cancel conditions defined. Message will be sent if segment rules match." }) })
|
|
322
|
+
] }),
|
|
323
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { marginTop: 3, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
324
|
+
designSystem.Button,
|
|
325
|
+
{
|
|
326
|
+
variant: "secondary",
|
|
327
|
+
startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Plus, {}),
|
|
328
|
+
onClick: handleAddRule,
|
|
329
|
+
disabled,
|
|
330
|
+
size: "S",
|
|
331
|
+
children: "Add Cancel Condition"
|
|
332
|
+
}
|
|
333
|
+
) })
|
|
334
|
+
] }) }) }),
|
|
335
|
+
error && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Error, { children: error }),
|
|
336
|
+
hint && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Hint, { children: hint })
|
|
337
|
+
] }) });
|
|
338
|
+
}
|
|
339
|
+
);
|
|
340
|
+
CancelConditionsField.displayName = "CancelConditionsField";
|
|
341
|
+
exports.default = CancelConditionsField;
|
|
@@ -16950,9 +16950,9 @@ const usersService = adminApi.enhanceEndpoints({
|
|
|
16950
16950
|
});
|
|
16951
16951
|
const { useCreateUserMutation, useGetUsersQuery, useUpdateUserMutation, useDeleteManyUsersMutation, useGetRolesQuery, useCreateRoleMutation, useUpdateRoleMutation, useGetRolePermissionsQuery, useGetRolePermissionLayoutQuery, useUpdateRolePermissionsMutation } = usersService;
|
|
16952
16952
|
const getBackendUrl = () => {
|
|
16953
|
-
const raw = process.env.STRAPI_ADMIN_CRM_BACKEND_URL || "http://localhost:3100
|
|
16953
|
+
const raw = process.env.STRAPI_ADMIN_CRM_BACKEND_URL || "http://localhost:3100";
|
|
16954
16954
|
if (!/^https?:\/\//i.test(raw)) {
|
|
16955
|
-
return "http://localhost:3100
|
|
16955
|
+
return "http://localhost:3100";
|
|
16956
16956
|
}
|
|
16957
16957
|
return raw.endsWith("/") ? raw.slice(0, -1) : raw;
|
|
16958
16958
|
};
|
|
@@ -16973,7 +16973,7 @@ const LogsTable = () => {
|
|
|
16973
16973
|
if (filterUserId) params.set("userId", filterUserId);
|
|
16974
16974
|
if (filterStatus) params.set("status", filterStatus);
|
|
16975
16975
|
const backendUrl = getBackendUrl();
|
|
16976
|
-
const res = await fetch(new URL(`/crm/logs?${params}`, backendUrl).toString());
|
|
16976
|
+
const res = await fetch(new URL(`/api/crm/logs?${params}`, backendUrl).toString());
|
|
16977
16977
|
if (res.ok) {
|
|
16978
16978
|
const data = await res.json();
|
|
16979
16979
|
setLogs(data.data || []);
|
|
@@ -17099,7 +17099,7 @@ const AntiSpamLogsTable = () => {
|
|
|
17099
17099
|
pageSize: "20"
|
|
17100
17100
|
});
|
|
17101
17101
|
const backendUrl = getBackendUrl();
|
|
17102
|
-
const res = await fetch(new URL(`/crm/anti-spam-logs?${params}`, backendUrl).toString());
|
|
17102
|
+
const res = await fetch(new URL(`/api/crm/anti-spam-logs?${params}`, backendUrl).toString());
|
|
17103
17103
|
if (res.ok) {
|
|
17104
17104
|
const data = await res.json();
|
|
17105
17105
|
setLogs(data.data || []);
|
|
@@ -17193,7 +17193,7 @@ const StatsView = () => {
|
|
|
17193
17193
|
setLoading(true);
|
|
17194
17194
|
try {
|
|
17195
17195
|
const backendUrl = getBackendUrl();
|
|
17196
|
-
const res = await fetch(new URL("/crm/stats", backendUrl).toString());
|
|
17196
|
+
const res = await fetch(new URL("/api/crm/stats", backendUrl).toString());
|
|
17197
17197
|
if (res.ok) {
|
|
17198
17198
|
const data = await res.json();
|
|
17199
17199
|
setStats(Array.isArray(data) ? data : []);
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef, useState, useEffect } from "react";
|
|
3
|
+
import { Field, Flex, Box, Typography, Card, CardContent, TextInput, SingleSelect, SingleSelectOption } from "@strapi/design-system";
|
|
4
|
+
const DEFAULT_CONFIG = {
|
|
5
|
+
type: "event_based",
|
|
6
|
+
eventName: "",
|
|
7
|
+
delayValue: 10,
|
|
8
|
+
delayUnit: "minutes",
|
|
9
|
+
scheduleType: "daily",
|
|
10
|
+
scheduleTime: "12:00",
|
|
11
|
+
scheduleDays: [1, 2, 3, 4, 5],
|
|
12
|
+
scheduleCron: "0 12 * * *"
|
|
13
|
+
};
|
|
14
|
+
const WEEKDAYS = [
|
|
15
|
+
{ value: 0, label: "Sun" },
|
|
16
|
+
{ value: 1, label: "Mon" },
|
|
17
|
+
{ value: 2, label: "Tue" },
|
|
18
|
+
{ value: 3, label: "Wed" },
|
|
19
|
+
{ value: 4, label: "Thu" },
|
|
20
|
+
{ value: 5, label: "Fri" },
|
|
21
|
+
{ value: 6, label: "Sat" }
|
|
22
|
+
];
|
|
23
|
+
const parseConfig = (value) => {
|
|
24
|
+
if (!value) return DEFAULT_CONFIG;
|
|
25
|
+
try {
|
|
26
|
+
const parsed = JSON.parse(value);
|
|
27
|
+
return {
|
|
28
|
+
type: parsed.type || "event_based",
|
|
29
|
+
eventName: parsed.eventName || "",
|
|
30
|
+
delayValue: parsed.delayValue ?? 10,
|
|
31
|
+
delayUnit: parsed.delayUnit || "minutes",
|
|
32
|
+
scheduleType: parsed.scheduleType || "daily",
|
|
33
|
+
scheduleTime: parsed.scheduleTime || "12:00",
|
|
34
|
+
scheduleDays: parsed.scheduleDays || [1, 2, 3, 4, 5],
|
|
35
|
+
scheduleCron: parsed.scheduleCron || "0 12 * * *"
|
|
36
|
+
};
|
|
37
|
+
} catch {
|
|
38
|
+
return DEFAULT_CONFIG;
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
const serializeConfig = (config) => {
|
|
42
|
+
return JSON.stringify(config);
|
|
43
|
+
};
|
|
44
|
+
const TriggerConfigField = forwardRef(
|
|
45
|
+
({ name, value, onChange, intlLabel, disabled, error, required, hint }, ref) => {
|
|
46
|
+
const [config, setConfig] = useState(() => parseConfig(value));
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
const parsed = parseConfig(value);
|
|
49
|
+
setConfig(parsed);
|
|
50
|
+
}, [value]);
|
|
51
|
+
const handleUpdate = (updates) => {
|
|
52
|
+
const newConfig = { ...config, ...updates };
|
|
53
|
+
setConfig(newConfig);
|
|
54
|
+
onChange({
|
|
55
|
+
target: {
|
|
56
|
+
name,
|
|
57
|
+
value: serializeConfig(newConfig)
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
};
|
|
61
|
+
const toggleDay = (day) => {
|
|
62
|
+
const days = config.scheduleDays || [];
|
|
63
|
+
const newDays = days.includes(day) ? days.filter((d) => d !== day) : [...days, day].sort((a, b) => a - b);
|
|
64
|
+
handleUpdate({ scheduleDays: newDays });
|
|
65
|
+
};
|
|
66
|
+
const isEventBased = config.type === "event_based";
|
|
67
|
+
const isDbBased = config.type === "db_based";
|
|
68
|
+
return /* @__PURE__ */ jsx(Field.Root, { name, error, required, hint, ref, children: /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 3, children: [
|
|
69
|
+
/* @__PURE__ */ jsx(Field.Label, { children: intlLabel?.defaultMessage || "Trigger Configuration" }),
|
|
70
|
+
/* @__PURE__ */ jsxs(Box, { children: [
|
|
71
|
+
/* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", style: { marginBottom: "8px", display: "block" }, children: "Campaign Type" }),
|
|
72
|
+
/* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
|
|
73
|
+
/* @__PURE__ */ jsxs(
|
|
74
|
+
Box,
|
|
75
|
+
{
|
|
76
|
+
padding: 3,
|
|
77
|
+
background: isEventBased ? "primary100" : "neutral100",
|
|
78
|
+
hasRadius: true,
|
|
79
|
+
style: {
|
|
80
|
+
cursor: disabled ? "not-allowed" : "pointer",
|
|
81
|
+
border: `2px solid ${isEventBased ? "#4945ff" : "#dcdce4"}`,
|
|
82
|
+
flex: 1,
|
|
83
|
+
textAlign: "center"
|
|
84
|
+
},
|
|
85
|
+
onClick: () => !disabled && handleUpdate({ type: "event_based" }),
|
|
86
|
+
children: [
|
|
87
|
+
/* @__PURE__ */ jsx(
|
|
88
|
+
Typography,
|
|
89
|
+
{
|
|
90
|
+
variant: "omega",
|
|
91
|
+
fontWeight: isEventBased ? "bold" : "regular",
|
|
92
|
+
textColor: isEventBased ? "primary600" : "neutral600",
|
|
93
|
+
children: "Event-Based"
|
|
94
|
+
}
|
|
95
|
+
),
|
|
96
|
+
/* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral500", children: "Fires on user action" })
|
|
97
|
+
]
|
|
98
|
+
}
|
|
99
|
+
),
|
|
100
|
+
/* @__PURE__ */ jsxs(
|
|
101
|
+
Box,
|
|
102
|
+
{
|
|
103
|
+
padding: 3,
|
|
104
|
+
background: isDbBased ? "primary100" : "neutral100",
|
|
105
|
+
hasRadius: true,
|
|
106
|
+
style: {
|
|
107
|
+
cursor: disabled ? "not-allowed" : "pointer",
|
|
108
|
+
border: `2px solid ${isDbBased ? "#4945ff" : "#dcdce4"}`,
|
|
109
|
+
flex: 1,
|
|
110
|
+
textAlign: "center"
|
|
111
|
+
},
|
|
112
|
+
onClick: () => !disabled && handleUpdate({ type: "db_based" }),
|
|
113
|
+
children: [
|
|
114
|
+
/* @__PURE__ */ jsx(
|
|
115
|
+
Typography,
|
|
116
|
+
{
|
|
117
|
+
variant: "omega",
|
|
118
|
+
fontWeight: isDbBased ? "bold" : "regular",
|
|
119
|
+
textColor: isDbBased ? "primary600" : "neutral600",
|
|
120
|
+
children: "DB-Based (Scheduled)"
|
|
121
|
+
}
|
|
122
|
+
),
|
|
123
|
+
/* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral500", children: "Runs on schedule" })
|
|
124
|
+
]
|
|
125
|
+
}
|
|
126
|
+
)
|
|
127
|
+
] })
|
|
128
|
+
] }),
|
|
129
|
+
isEventBased && /* @__PURE__ */ jsx(Card, { background: "neutral100", children: /* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 4, padding: 4, children: [
|
|
130
|
+
/* @__PURE__ */ jsx(Typography, { variant: "delta", children: "Event Configuration" }),
|
|
131
|
+
/* @__PURE__ */ jsxs(Field.Root, { name: `${name}-eventName`, children: [
|
|
132
|
+
/* @__PURE__ */ jsx(Field.Label, { children: "Event Name" }),
|
|
133
|
+
/* @__PURE__ */ jsx(
|
|
134
|
+
TextInput,
|
|
135
|
+
{
|
|
136
|
+
placeholder: "e.g., gg-case-deposit",
|
|
137
|
+
value: config.eventName || "",
|
|
138
|
+
onChange: (e) => handleUpdate({ eventName: e.target.value }),
|
|
139
|
+
disabled
|
|
140
|
+
}
|
|
141
|
+
),
|
|
142
|
+
/* @__PURE__ */ jsx(Field.Hint, { children: "The event that triggers this campaign (e.g., gg-case-deposit, gg-deposit-suggestion-selected)" })
|
|
143
|
+
] }),
|
|
144
|
+
/* @__PURE__ */ jsxs(Box, { children: [
|
|
145
|
+
/* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral800", style: { marginBottom: "8px", display: "block" }, children: "Delay Before Sending" }),
|
|
146
|
+
/* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "flex-start", children: [
|
|
147
|
+
/* @__PURE__ */ jsx(Box, { style: { width: "120px" }, children: /* @__PURE__ */ jsx(
|
|
148
|
+
TextInput,
|
|
149
|
+
{
|
|
150
|
+
type: "number",
|
|
151
|
+
value: String(config.delayValue || 0),
|
|
152
|
+
onChange: (e) => handleUpdate({ delayValue: parseInt(e.target.value, 10) || 0 }),
|
|
153
|
+
disabled,
|
|
154
|
+
"aria-label": "Delay value"
|
|
155
|
+
}
|
|
156
|
+
) }),
|
|
157
|
+
/* @__PURE__ */ jsx(Box, { style: { width: "150px" }, children: /* @__PURE__ */ jsxs(
|
|
158
|
+
SingleSelect,
|
|
159
|
+
{
|
|
160
|
+
value: config.delayUnit || "minutes",
|
|
161
|
+
onChange: (val) => handleUpdate({ delayUnit: val }),
|
|
162
|
+
disabled,
|
|
163
|
+
children: [
|
|
164
|
+
/* @__PURE__ */ jsx(SingleSelectOption, { value: "seconds", children: "seconds" }),
|
|
165
|
+
/* @__PURE__ */ jsx(SingleSelectOption, { value: "minutes", children: "minutes" }),
|
|
166
|
+
/* @__PURE__ */ jsx(SingleSelectOption, { value: "hours", children: "hours" })
|
|
167
|
+
]
|
|
168
|
+
}
|
|
169
|
+
) })
|
|
170
|
+
] }),
|
|
171
|
+
/* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral500", style: { marginTop: "4px" }, children: "Wait this long after the event before checking segment rules and sending" })
|
|
172
|
+
] })
|
|
173
|
+
] }) }) }),
|
|
174
|
+
isDbBased && /* @__PURE__ */ jsx(Card, { background: "neutral100", children: /* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 4, padding: 4, children: [
|
|
175
|
+
/* @__PURE__ */ jsx(Typography, { variant: "delta", children: "Schedule Configuration" }),
|
|
176
|
+
/* @__PURE__ */ jsxs(Field.Root, { name: `${name}-scheduleType`, children: [
|
|
177
|
+
/* @__PURE__ */ jsx(Field.Label, { children: "Schedule Type" }),
|
|
178
|
+
/* @__PURE__ */ jsxs(
|
|
179
|
+
SingleSelect,
|
|
180
|
+
{
|
|
181
|
+
value: config.scheduleType || "daily",
|
|
182
|
+
onChange: (val) => handleUpdate({ scheduleType: val }),
|
|
183
|
+
disabled,
|
|
184
|
+
children: [
|
|
185
|
+
/* @__PURE__ */ jsx(SingleSelectOption, { value: "daily", children: "Daily" }),
|
|
186
|
+
/* @__PURE__ */ jsx(SingleSelectOption, { value: "weekly", children: "Weekly" }),
|
|
187
|
+
/* @__PURE__ */ jsx(SingleSelectOption, { value: "cron", children: "Custom (Cron)" })
|
|
188
|
+
]
|
|
189
|
+
}
|
|
190
|
+
)
|
|
191
|
+
] }),
|
|
192
|
+
(config.scheduleType === "daily" || config.scheduleType === "weekly") && /* @__PURE__ */ jsxs(Field.Root, { name: `${name}-scheduleTime`, children: [
|
|
193
|
+
/* @__PURE__ */ jsx(Field.Label, { children: "Time of Day (UTC)" }),
|
|
194
|
+
/* @__PURE__ */ jsx(
|
|
195
|
+
TextInput,
|
|
196
|
+
{
|
|
197
|
+
type: "time",
|
|
198
|
+
value: config.scheduleTime || "12:00",
|
|
199
|
+
onChange: (e) => handleUpdate({ scheduleTime: e.target.value }),
|
|
200
|
+
disabled
|
|
201
|
+
}
|
|
202
|
+
)
|
|
203
|
+
] }),
|
|
204
|
+
config.scheduleType === "weekly" && /* @__PURE__ */ jsxs(Box, { children: [
|
|
205
|
+
/* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral800", style: { marginBottom: "8px", display: "block" }, children: "Days of Week" }),
|
|
206
|
+
/* @__PURE__ */ jsx(Flex, { gap: 2, wrap: "wrap", children: WEEKDAYS.map((day) => /* @__PURE__ */ jsx(
|
|
207
|
+
Box,
|
|
208
|
+
{
|
|
209
|
+
padding: 2,
|
|
210
|
+
background: (config.scheduleDays || []).includes(day.value) ? "primary100" : "neutral100",
|
|
211
|
+
hasRadius: true,
|
|
212
|
+
style: {
|
|
213
|
+
cursor: disabled ? "not-allowed" : "pointer",
|
|
214
|
+
border: `1px solid ${(config.scheduleDays || []).includes(day.value) ? "#4945ff" : "#dcdce4"}`,
|
|
215
|
+
minWidth: "50px",
|
|
216
|
+
textAlign: "center"
|
|
217
|
+
},
|
|
218
|
+
onClick: () => !disabled && toggleDay(day.value),
|
|
219
|
+
children: /* @__PURE__ */ jsx(
|
|
220
|
+
Typography,
|
|
221
|
+
{
|
|
222
|
+
variant: "omega",
|
|
223
|
+
textColor: (config.scheduleDays || []).includes(day.value) ? "primary600" : "neutral600",
|
|
224
|
+
children: day.label
|
|
225
|
+
}
|
|
226
|
+
)
|
|
227
|
+
},
|
|
228
|
+
day.value
|
|
229
|
+
)) })
|
|
230
|
+
] }),
|
|
231
|
+
config.scheduleType === "cron" && /* @__PURE__ */ jsxs(Field.Root, { name: `${name}-scheduleCron`, children: [
|
|
232
|
+
/* @__PURE__ */ jsx(Field.Label, { children: "Cron Expression" }),
|
|
233
|
+
/* @__PURE__ */ jsx(
|
|
234
|
+
TextInput,
|
|
235
|
+
{
|
|
236
|
+
placeholder: "0 12 * * *",
|
|
237
|
+
value: config.scheduleCron || "",
|
|
238
|
+
onChange: (e) => handleUpdate({ scheduleCron: e.target.value }),
|
|
239
|
+
disabled
|
|
240
|
+
}
|
|
241
|
+
),
|
|
242
|
+
/* @__PURE__ */ jsx(Field.Hint, { children: 'Standard cron format: minute hour day month weekday (e.g., "0 12 * * *" = daily at 12:00 UTC)' })
|
|
243
|
+
] })
|
|
244
|
+
] }) }) }),
|
|
245
|
+
error && /* @__PURE__ */ jsx(Field.Error, { children: error }),
|
|
246
|
+
hint && /* @__PURE__ */ jsx(Field.Hint, { children: hint })
|
|
247
|
+
] }) });
|
|
248
|
+
}
|
|
249
|
+
);
|
|
250
|
+
TriggerConfigField.displayName = "TriggerConfigField";
|
|
251
|
+
export {
|
|
252
|
+
TriggerConfigField as default
|
|
253
|
+
};
|