@carlonicora/nextjs-jsonapi 5.2.0 → 5.3.0

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 (134) hide show
  1. package/dist/{BlockNoteEditor-EGY4ZAAM.js → BlockNoteEditor-UJ6363LC.js} +19 -19
  2. package/dist/{BlockNoteEditor-EGY4ZAAM.js.map → BlockNoteEditor-UJ6363LC.js.map} +1 -1
  3. package/dist/{BlockNoteEditor-2QJVK2HH.mjs → BlockNoteEditor-WTSQANKI.mjs} +4 -4
  4. package/dist/{BlockNoteViewer-I2MMFH3S.js → BlockNoteViewer-EL4JFB6P.js} +3 -3
  5. package/dist/{BlockNoteViewer-I2MMFH3S.js.map → BlockNoteViewer-EL4JFB6P.js.map} +1 -1
  6. package/dist/{BlockNoteViewer-6BQXJ4TB.mjs → BlockNoteViewer-JVKWJZ6J.mjs} +2 -2
  7. package/dist/billing/index.js +441 -441
  8. package/dist/billing/index.mjs +3 -3
  9. package/dist/{chunk-3VEQ5JZJ.js → chunk-3BAM23VA.js} +847 -847
  10. package/dist/chunk-3BAM23VA.js.map +1 -0
  11. package/dist/{chunk-JEZQTZQ4.mjs → chunk-6WU74VFU.mjs} +2 -2
  12. package/dist/{chunk-S5MQDWWC.js → chunk-CRI4T47Y.js} +736 -176
  13. package/dist/chunk-CRI4T47Y.js.map +1 -0
  14. package/dist/{chunk-75MLT74B.js → chunk-EO75CV76.js} +8 -8
  15. package/dist/{chunk-75MLT74B.js.map → chunk-EO75CV76.js.map} +1 -1
  16. package/dist/{chunk-NPKFMRH5.mjs → chunk-FNTQQFLH.mjs} +2 -2
  17. package/dist/{chunk-HNOWSFM5.mjs → chunk-JQWI62NV.mjs} +563 -3
  18. package/dist/chunk-JQWI62NV.mjs.map +1 -0
  19. package/dist/{chunk-2Z3ZCYXX.mjs → chunk-MIZTWBPX.mjs} +5 -5
  20. package/dist/chunk-MIZTWBPX.mjs.map +1 -0
  21. package/dist/{chunk-YMUPTXOT.mjs → chunk-QKZUE5OF.mjs} +2 -2
  22. package/dist/{chunk-EXICQC4A.js → chunk-QQQE3N2X.js} +3 -3
  23. package/dist/{chunk-EXICQC4A.js.map → chunk-QQQE3N2X.js.map} +1 -1
  24. package/dist/{chunk-THKYH65N.js → chunk-XGTQWBDJ.js} +4 -4
  25. package/dist/{chunk-THKYH65N.js.map → chunk-XGTQWBDJ.js.map} +1 -1
  26. package/dist/client/index.d.mts +6 -39
  27. package/dist/client/index.d.ts +6 -39
  28. package/dist/client/index.js +4 -4
  29. package/dist/client/index.mjs +3 -3
  30. package/dist/components/index.d.mts +2 -2
  31. package/dist/components/index.d.ts +2 -2
  32. package/dist/components/index.js +4 -4
  33. package/dist/components/index.mjs +3 -3
  34. package/dist/contexts/index.js +4 -4
  35. package/dist/contexts/index.mjs +3 -3
  36. package/dist/core/index.d.mts +187 -1
  37. package/dist/core/index.d.ts +187 -1
  38. package/dist/core/index.js +30 -2
  39. package/dist/core/index.js.map +1 -1
  40. package/dist/core/index.mjs +29 -1
  41. package/dist/email-automation-enrollment.fields-BZvF14qx.d.mts +82 -0
  42. package/dist/email-automation-enrollment.fields-Cp3VMzFq.d.ts +82 -0
  43. package/dist/email-automations/index.css +30 -0
  44. package/dist/email-automations/index.css.map +1 -0
  45. package/dist/email-automations/index.d.mts +303 -0
  46. package/dist/email-automations/index.d.ts +303 -0
  47. package/dist/email-automations/index.js +1705 -0
  48. package/dist/email-automations/index.js.map +1 -0
  49. package/dist/email-automations/index.mjs +1705 -0
  50. package/dist/email-automations/index.mjs.map +1 -0
  51. package/dist/features/analytics/index.js +122 -122
  52. package/dist/features/analytics/index.mjs +4 -4
  53. package/dist/features/analytics/server.js +4 -4
  54. package/dist/features/analytics/server.mjs +2 -2
  55. package/dist/features/help/index.js +37 -37
  56. package/dist/features/help/index.mjs +3 -3
  57. package/dist/features/tokenusage/index.js +125 -125
  58. package/dist/features/tokenusage/index.mjs +4 -4
  59. package/dist/index.d.mts +2 -1
  60. package/dist/index.d.ts +2 -1
  61. package/dist/index.js +31 -3
  62. package/dist/index.js.map +1 -1
  63. package/dist/index.mjs +30 -2
  64. package/dist/server/index.js +3 -3
  65. package/dist/server/index.mjs +2 -2
  66. package/dist/types-70acd-8C.d.mts +39 -0
  67. package/dist/types-l8SFleXi.d.ts +39 -0
  68. package/messages/en.json +117 -0
  69. package/messages/it.json +117 -0
  70. package/package.json +11 -1
  71. package/src/components/forms/FormFieldWrapper.tsx +1 -1
  72. package/src/components/forms/FormTextarea.tsx +2 -1
  73. package/src/core/index.ts +8 -0
  74. package/src/core/registry/ModuleRegistry.ts +5 -0
  75. package/src/email-automations/index.ts +5 -0
  76. package/src/features/email-automation/EmailAutomationEnrollmentModule.ts +11 -0
  77. package/src/features/email-automation/EmailAutomationModule.ts +30 -0
  78. package/src/features/email-automation/EmailAutomationStepModule.ts +11 -0
  79. package/src/features/email-automation/EmailAutomationTriggerModule.ts +11 -0
  80. package/src/features/email-automation/components/__tests__/EmailAutomationEditor.spec.tsx +104 -0
  81. package/src/features/email-automation/components/canvas/EmailAutomationActiveSwitch.tsx +49 -0
  82. package/src/features/email-automation/components/canvas/EmailAutomationCanvas.tsx +474 -0
  83. package/src/features/email-automation/components/canvas/EmailAutomationCanvasInspector.tsx +89 -0
  84. package/src/features/email-automation/components/canvas/EmailAutomationCanvasTab.tsx +32 -0
  85. package/src/features/email-automation/components/canvas/__tests__/buildCanvasGraph.spec.ts +67 -0
  86. package/src/features/email-automation/components/canvas/buildCanvasGraph.ts +141 -0
  87. package/src/features/email-automation/components/canvas/edges/DelayEdge.tsx +68 -0
  88. package/src/features/email-automation/components/canvas/nodes/EmailStepNode.tsx +43 -0
  89. package/src/features/email-automation/components/canvas/nodes/TriggerNode.tsx +23 -0
  90. package/src/features/email-automation/components/canvas/shared/CanvasToolbar.tsx +53 -0
  91. package/src/features/email-automation/components/canvas/shared/EmptyCanvas.tsx +11 -0
  92. package/src/features/email-automation/components/canvas/shared/__tests__/autoLayout.spec.ts +42 -0
  93. package/src/features/email-automation/components/canvas/shared/__tests__/longPress.spec.ts +51 -0
  94. package/src/features/email-automation/components/canvas/shared/autoLayout.ts +55 -0
  95. package/src/features/email-automation/components/canvas/shared/index.ts +4 -0
  96. package/src/features/email-automation/components/canvas/shared/longPress.ts +57 -0
  97. package/src/features/email-automation/components/containers/EmailAutomationContainer.tsx +80 -0
  98. package/src/features/email-automation/components/containers/EmailAutomationsListContainer.tsx +27 -0
  99. package/src/features/email-automation/components/forms/EmailAutomationEditor.tsx +153 -0
  100. package/src/features/email-automation/components/forms/EmailStepEditor.tsx +151 -0
  101. package/src/features/email-automation/components/index.ts +15 -0
  102. package/src/features/email-automation/components/lists/EmailAutomationRecipientsList.tsx +90 -0
  103. package/src/features/email-automation/components/lists/EmailAutomationsList.tsx +76 -0
  104. package/src/features/email-automation/components/unsubscribe/EmailUnsubscribe.tsx +99 -0
  105. package/src/features/email-automation/components/unsubscribe/__tests__/EmailUnsubscribe.spec.tsx +55 -0
  106. package/src/features/email-automation/contexts/EmailAutomationContext.tsx +175 -0
  107. package/src/features/email-automation/data/EmailAutomation.ts +87 -0
  108. package/src/features/email-automation/data/EmailAutomationEnrollment.ts +105 -0
  109. package/src/features/email-automation/data/EmailAutomationEnrollmentInterface.ts +18 -0
  110. package/src/features/email-automation/data/EmailAutomationEnrollmentService.ts +31 -0
  111. package/src/features/email-automation/data/EmailAutomationInterface.ts +22 -0
  112. package/src/features/email-automation/data/EmailAutomationService.ts +106 -0
  113. package/src/features/email-automation/data/EmailAutomationStep.ts +104 -0
  114. package/src/features/email-automation/data/EmailAutomationStepInterface.ts +23 -0
  115. package/src/features/email-automation/data/EmailAutomationStepService.ts +80 -0
  116. package/src/features/email-automation/data/EmailAutomationTrigger.ts +28 -0
  117. package/src/features/email-automation/data/EmailAutomationTriggerInterface.ts +6 -0
  118. package/src/features/email-automation/data/__tests__/email-automation-models.spec.ts +103 -0
  119. package/src/features/email-automation/data/email-automation-enrollment.fields.ts +8 -0
  120. package/src/features/email-automation/data/email-automation.fields.ts +9 -0
  121. package/src/features/email-automation/data/index.ts +13 -0
  122. package/src/features/email-automation/hooks/index.ts +2 -0
  123. package/src/features/email-automation/hooks/useEmailAutomationEnrollmentTableStructure.tsx +126 -0
  124. package/src/features/email-automation/hooks/useEmailAutomationTableStructure.tsx +119 -0
  125. package/src/features/email-automation/index.ts +5 -0
  126. package/dist/chunk-2Z3ZCYXX.mjs.map +0 -1
  127. package/dist/chunk-3VEQ5JZJ.js.map +0 -1
  128. package/dist/chunk-HNOWSFM5.mjs.map +0 -1
  129. package/dist/chunk-S5MQDWWC.js.map +0 -1
  130. /package/dist/{BlockNoteEditor-2QJVK2HH.mjs.map → BlockNoteEditor-WTSQANKI.mjs.map} +0 -0
  131. /package/dist/{BlockNoteViewer-6BQXJ4TB.mjs.map → BlockNoteViewer-JVKWJZ6J.mjs.map} +0 -0
  132. /package/dist/{chunk-JEZQTZQ4.mjs.map → chunk-6WU74VFU.mjs.map} +0 -0
  133. /package/dist/{chunk-NPKFMRH5.mjs.map → chunk-FNTQQFLH.mjs.map} +0 -0
  134. /package/dist/{chunk-YMUPTXOT.mjs.map → chunk-QKZUE5OF.mjs.map} +0 -0
@@ -0,0 +1,1705 @@
1
+ "use client";
2
+ import {
3
+ AdministrationProvider,
4
+ Badge,
5
+ Button,
6
+ ConfirmDialog,
7
+ ContentListTable,
8
+ ContextMenu,
9
+ ContextMenuContent,
10
+ ContextMenuItem,
11
+ EditorSheet,
12
+ EmptyState,
13
+ FormInput,
14
+ FormSelect,
15
+ FormSwitch,
16
+ FormTextarea,
17
+ Link,
18
+ RoundPageContainer,
19
+ SectionHeader,
20
+ Select,
21
+ SelectContent,
22
+ SelectItem,
23
+ SelectTrigger,
24
+ SelectValue,
25
+ SharedProvider,
26
+ Switch,
27
+ Tooltip,
28
+ TooltipContent,
29
+ TooltipTrigger,
30
+ errorToast,
31
+ registerTableGenerator,
32
+ useDataListRetriever,
33
+ usePageUrlGenerator,
34
+ useSidebar
35
+ } from "../chunk-MIZTWBPX.mjs";
36
+ import "../chunk-6WU74VFU.mjs";
37
+ import "../chunk-PW42LRSL.mjs";
38
+ import {
39
+ EmailAutomationEnrollmentService,
40
+ EmailAutomationService,
41
+ EmailAutomationStepService,
42
+ Modules,
43
+ dateFormatLocale,
44
+ formatDate,
45
+ showError,
46
+ useI18nRouter,
47
+ useIsMobile
48
+ } from "../chunk-JQWI62NV.mjs";
49
+ import "../chunk-AUXK7QSA.mjs";
50
+ import "../chunk-PI56A4DG.mjs";
51
+ import "../chunk-V5MI7RNB.mjs";
52
+ import "../chunk-VOXD3ZLY.mjs";
53
+ import {
54
+ __name
55
+ } from "../chunk-PAWJFY3S.mjs";
56
+
57
+ // src/features/email-automation/components/lists/EmailAutomationsList.tsx
58
+ import { MailIcon } from "lucide-react";
59
+ import { useTranslations as useTranslations2 } from "next-intl";
60
+ import { useEffect as useEffect2, useMemo as useMemo2, useState as useState2 } from "react";
61
+
62
+ // src/features/email-automation/components/forms/EmailAutomationEditor.tsx
63
+ import { zodResolver } from "@hookform/resolvers/zod";
64
+ import { useTranslations } from "next-intl";
65
+ import { useCallback, useEffect, useMemo, useState } from "react";
66
+ import { useForm } from "react-hook-form";
67
+ import { v4 } from "uuid";
68
+ import { z } from "zod";
69
+ import { jsx, jsxs } from "react/jsx-runtime";
70
+ function EmailAutomationEditorInternal({
71
+ automation,
72
+ propagateChanges,
73
+ onSuccess,
74
+ trigger,
75
+ forceShow,
76
+ onClose,
77
+ dialogOpen,
78
+ onDialogOpenChange
79
+ }) {
80
+ const t = useTranslations();
81
+ const router = useI18nRouter();
82
+ const isEdit = !!automation;
83
+ const [triggers, setTriggers] = useState([]);
84
+ useEffect(() => {
85
+ let active = true;
86
+ EmailAutomationService.findTriggers().then((list) => {
87
+ if (active) setTriggers(list);
88
+ }).catch(() => {
89
+ });
90
+ return () => {
91
+ active = false;
92
+ };
93
+ }, []);
94
+ const triggerValues = useMemo(() => triggers.map((entry) => ({ id: entry.key, text: entry.label })), [triggers]);
95
+ const formSchema = useMemo(
96
+ () => z.object({
97
+ id: z.uuidv4(),
98
+ name: z.string().min(1, { message: t("emailAutomations.fields.name.error") }),
99
+ trigger: z.string().optional(),
100
+ exitTrigger: z.string().optional(),
101
+ replyTo: z.string().refine((value) => value === "" || z.email().safeParse(value).success, {
102
+ message: t("emailAutomations.fields.replyTo.error")
103
+ }),
104
+ isActive: z.boolean()
105
+ }),
106
+ [t]
107
+ );
108
+ const getDefaultValues = useCallback(
109
+ () => ({
110
+ id: automation?.id ?? v4(),
111
+ name: automation?.name ?? "",
112
+ trigger: automation?.trigger ?? "",
113
+ exitTrigger: automation?.exitTrigger ?? "",
114
+ replyTo: automation?.replyTo ?? "",
115
+ isActive: automation?.isActive ?? false
116
+ }),
117
+ [automation]
118
+ );
119
+ const form = useForm({
120
+ resolver: zodResolver(formSchema),
121
+ defaultValues: getDefaultValues()
122
+ });
123
+ return /* @__PURE__ */ jsx(
124
+ EditorSheet,
125
+ {
126
+ form,
127
+ entityType: t("emailAutomations.admin.title"),
128
+ entityName: automation?.name,
129
+ title: isEdit ? t("emailAutomations.admin.editAutomation") : t("emailAutomations.admin.newAutomation"),
130
+ isEdit,
131
+ module: Modules.EmailAutomation,
132
+ size: "md",
133
+ propagateChanges,
134
+ onSuccess,
135
+ onNavigate: (url) => router.push(url),
136
+ onSubmit: async (values) => {
137
+ const payload = {
138
+ id: values.id,
139
+ name: values.name,
140
+ trigger: values.trigger || void 0,
141
+ exitTrigger: values.exitTrigger || void 0,
142
+ replyTo: values.replyTo || void 0,
143
+ isActive: values.isActive
144
+ };
145
+ return isEdit ? await EmailAutomationService.update(payload) : await EmailAutomationService.create(payload);
146
+ },
147
+ onReset: getDefaultValues,
148
+ trigger,
149
+ forceShow,
150
+ onClose,
151
+ dialogOpen,
152
+ onDialogOpenChange,
153
+ children: /* @__PURE__ */ jsxs("div", { className: "flex w-full flex-col gap-y-4", children: [
154
+ /* @__PURE__ */ jsx(FormInput, { form, id: "name", name: t("emailAutomations.fields.name.label"), isRequired: true }),
155
+ /* @__PURE__ */ jsx(
156
+ FormSelect,
157
+ {
158
+ form,
159
+ id: "trigger",
160
+ name: t("emailAutomations.fields.trigger.label"),
161
+ placeholder: t("emailAutomations.fields.trigger.none"),
162
+ values: triggerValues,
163
+ allowEmpty: true
164
+ }
165
+ ),
166
+ /* @__PURE__ */ jsx(
167
+ FormSelect,
168
+ {
169
+ form,
170
+ id: "exitTrigger",
171
+ name: t("emailAutomations.fields.exitTrigger.label"),
172
+ placeholder: t("emailAutomations.fields.trigger.none"),
173
+ values: triggerValues,
174
+ allowEmpty: true
175
+ }
176
+ ),
177
+ /* @__PURE__ */ jsx(FormInput, { form, id: "replyTo", name: t("emailAutomations.fields.replyTo.label") }),
178
+ /* @__PURE__ */ jsx(
179
+ FormSwitch,
180
+ {
181
+ form,
182
+ id: "isActive",
183
+ name: t("emailAutomations.fields.isActive.label"),
184
+ description: t("emailAutomations.fields.isActive.help")
185
+ }
186
+ )
187
+ ] })
188
+ }
189
+ );
190
+ }
191
+ __name(EmailAutomationEditorInternal, "EmailAutomationEditorInternal");
192
+ function EmailAutomationEditor(props) {
193
+ return /* @__PURE__ */ jsx(EmailAutomationEditorInternal, { ...props });
194
+ }
195
+ __name(EmailAutomationEditor, "EmailAutomationEditor");
196
+ var EmailAutomationEditor_default = EmailAutomationEditor;
197
+
198
+ // src/features/email-automation/components/lists/EmailAutomationsList.tsx
199
+ import { jsx as jsx2 } from "react/jsx-runtime";
200
+ function EmailAutomationsList({ fullWidth } = {}) {
201
+ const t = useTranslations2();
202
+ const [triggers, setTriggers] = useState2([]);
203
+ const data = useDataListRetriever({
204
+ retriever: /* @__PURE__ */ __name((params) => EmailAutomationService.findMany(params), "retriever"),
205
+ retrieverParams: {},
206
+ module: Modules.EmailAutomation
207
+ });
208
+ useEffect2(() => {
209
+ let active = true;
210
+ EmailAutomationService.findTriggers().then((list) => {
211
+ if (active) setTriggers(list);
212
+ }).catch(() => {
213
+ });
214
+ return () => {
215
+ active = false;
216
+ };
217
+ }, []);
218
+ const triggerLabels = useMemo2(
219
+ () => Object.fromEntries(triggers.map((trigger) => [trigger.key, trigger.label])),
220
+ [triggers]
221
+ );
222
+ const functions = [/* @__PURE__ */ jsx2(EmailAutomationEditor_default, {}, "create-email-automation")];
223
+ return /* @__PURE__ */ jsx2(
224
+ ContentListTable,
225
+ {
226
+ data,
227
+ fields: [
228
+ "name" /* name */,
229
+ "trigger" /* trigger */,
230
+ "status" /* status */,
231
+ "steps" /* steps */,
232
+ "active" /* active */,
233
+ "completed" /* completed */,
234
+ "unsubscribed" /* unsubscribed */
235
+ ],
236
+ tableGeneratorType: Modules.EmailAutomation,
237
+ functions,
238
+ fullWidth,
239
+ title: t("emailAutomations.admin.title"),
240
+ context: { triggerLabels },
241
+ emptyState: /* @__PURE__ */ jsx2(EmptyState, { icon: MailIcon, title: t("emailAutomations.admin.empty") })
242
+ }
243
+ );
244
+ }
245
+ __name(EmailAutomationsList, "EmailAutomationsList");
246
+
247
+ // src/features/email-automation/components/containers/EmailAutomationsListContainer.tsx
248
+ import { jsx as jsx3 } from "react/jsx-runtime";
249
+ function EmailAutomationsListContainer() {
250
+ return /* @__PURE__ */ jsx3(AdministrationProvider, { children: /* @__PURE__ */ jsx3(RoundPageContainer, { module: Modules.EmailAutomation, fullWidth: true, children: /* @__PURE__ */ jsx3(EmailAutomationsList, { fullWidth: true }) }) });
251
+ }
252
+ __name(EmailAutomationsListContainer, "EmailAutomationsListContainer");
253
+
254
+ // src/features/email-automation/components/containers/EmailAutomationContainer.tsx
255
+ import { useTranslations as useTranslations9 } from "next-intl";
256
+
257
+ // src/features/email-automation/contexts/EmailAutomationContext.tsx
258
+ import { createContext, useCallback as useCallback2, useContext, useEffect as useEffect3, useMemo as useMemo3, useState as useState3 } from "react";
259
+ import { v4 as v42 } from "uuid";
260
+ import { jsx as jsx4 } from "react/jsx-runtime";
261
+ var EmailAutomationContext = createContext(void 0);
262
+ var EmailAutomationProvider = /* @__PURE__ */ __name(({ automationId, children }) => {
263
+ const [automation, setAutomation] = useState3(void 0);
264
+ const [steps, setSteps] = useState3([]);
265
+ const [triggers, setTriggers] = useState3([]);
266
+ const [selectedStepId, setSelectedStepId] = useState3(null);
267
+ const reload = useCallback2(async () => {
268
+ const [loadedAutomation, loadedSteps] = await Promise.all([
269
+ EmailAutomationService.findOne({ id: automationId }),
270
+ EmailAutomationStepService.findByAutomation({ automationId })
271
+ ]);
272
+ setAutomation(loadedAutomation);
273
+ setSteps(loadedSteps);
274
+ }, [automationId]);
275
+ useEffect3(() => {
276
+ void reload();
277
+ }, [reload]);
278
+ useEffect3(() => {
279
+ let active = true;
280
+ EmailAutomationService.findTriggers().then((list) => {
281
+ if (active) setTriggers(list);
282
+ }).catch(() => {
283
+ });
284
+ return () => {
285
+ active = false;
286
+ };
287
+ }, []);
288
+ const select = useCallback2((id) => setSelectedStepId(id), []);
289
+ const createStep = useCallback2(
290
+ async (input) => {
291
+ const saved = await EmailAutomationStepService.create({ ...input, id: input.id ?? v42(), automationId });
292
+ await reload();
293
+ return saved;
294
+ },
295
+ [automationId, reload]
296
+ );
297
+ const updateStep = useCallback2(
298
+ async (input) => {
299
+ const saved = await EmailAutomationStepService.update({ ...input, automationId });
300
+ await reload();
301
+ return saved;
302
+ },
303
+ [automationId, reload]
304
+ );
305
+ const deleteStep = useCallback2(
306
+ async (id) => {
307
+ await EmailAutomationStepService.delete({ id });
308
+ if (selectedStepId === id) setSelectedStepId(null);
309
+ await reload();
310
+ },
311
+ [reload, selectedStepId]
312
+ );
313
+ const setNext = useCallback2(
314
+ async (stepId, nextStepId) => {
315
+ if (nextStepId) await EmailAutomationStepService.setNext({ stepId, nextStepId });
316
+ else await EmailAutomationStepService.clearNext({ stepId });
317
+ await reload();
318
+ },
319
+ [reload]
320
+ );
321
+ const updatePosition = useCallback2(
322
+ async (stepId, x, y) => {
323
+ const step = steps.find((candidate) => candidate.id === stepId);
324
+ if (!step) return;
325
+ await EmailAutomationStepService.update({
326
+ id: step.id,
327
+ automationId,
328
+ subject: step.subject,
329
+ body: step.body,
330
+ delayHours: step.delayHours,
331
+ positionX: x,
332
+ positionY: y
333
+ });
334
+ await reload();
335
+ },
336
+ [automationId, reload, steps]
337
+ );
338
+ const tidy = useCallback2(async () => {
339
+ await EmailAutomationService.tidy({ id: automationId });
340
+ await reload();
341
+ }, [automationId, reload]);
342
+ const value = useMemo3(
343
+ () => ({
344
+ automation,
345
+ steps,
346
+ reload,
347
+ selectedStepId,
348
+ select,
349
+ createStep,
350
+ updateStep,
351
+ deleteStep,
352
+ setNext,
353
+ updatePosition,
354
+ tidy,
355
+ triggers
356
+ }),
357
+ [
358
+ automation,
359
+ steps,
360
+ reload,
361
+ selectedStepId,
362
+ select,
363
+ createStep,
364
+ updateStep,
365
+ deleteStep,
366
+ setNext,
367
+ updatePosition,
368
+ tidy,
369
+ triggers
370
+ ]
371
+ );
372
+ return /* @__PURE__ */ jsx4(EmailAutomationContext.Provider, { value, children });
373
+ }, "EmailAutomationProvider");
374
+ var useEmailAutomation = /* @__PURE__ */ __name(() => {
375
+ const context = useContext(EmailAutomationContext);
376
+ if (context === void 0) {
377
+ throw new Error("useEmailAutomation must be used within an EmailAutomationProvider");
378
+ }
379
+ return context;
380
+ }, "useEmailAutomation");
381
+
382
+ // src/features/email-automation/components/canvas/EmailAutomationActiveSwitch.tsx
383
+ import { useTranslations as useTranslations3 } from "next-intl";
384
+ import { useState as useState4 } from "react";
385
+ import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
386
+ function EmailAutomationActiveSwitch() {
387
+ const t = useTranslations3();
388
+ const { automation, reload } = useEmailAutomation();
389
+ const [saving, setSaving] = useState4(false);
390
+ if (!automation) return null;
391
+ const toggle = /* @__PURE__ */ __name(async (checked) => {
392
+ setSaving(true);
393
+ try {
394
+ await EmailAutomationService.update({
395
+ id: automation.id,
396
+ name: automation.name,
397
+ trigger: automation.trigger,
398
+ exitTrigger: automation.exitTrigger,
399
+ replyTo: automation.replyTo,
400
+ isActive: checked
401
+ });
402
+ await reload();
403
+ } catch (error) {
404
+ errorToast({ title: t("common.errors.error"), error });
405
+ } finally {
406
+ setSaving(false);
407
+ }
408
+ }, "toggle");
409
+ return /* @__PURE__ */ jsxs2(Tooltip, { children: [
410
+ /* @__PURE__ */ jsxs2(TooltipTrigger, { render: /* @__PURE__ */ jsx5("label", { className: "flex items-center gap-x-2" }), children: [
411
+ /* @__PURE__ */ jsx5(Switch, { checked: automation.isActive, onCheckedChange: (checked) => void toggle(checked), disabled: saving }),
412
+ /* @__PURE__ */ jsx5("span", { className: "text-xs", children: t("emailAutomations.fields.isActive.label") })
413
+ ] }),
414
+ /* @__PURE__ */ jsx5(TooltipContent, { className: "max-w-xs", children: t("emailAutomations.fields.isActive.help") })
415
+ ] });
416
+ }
417
+ __name(EmailAutomationActiveSwitch, "EmailAutomationActiveSwitch");
418
+
419
+ // src/features/email-automation/components/canvas/EmailAutomationCanvasTab.tsx
420
+ import { ReactFlowProvider } from "@xyflow/react";
421
+ import { useEffect as useEffect5 } from "react";
422
+
423
+ // src/features/email-automation/components/canvas/EmailAutomationCanvas.tsx
424
+ import {
425
+ Background,
426
+ ReactFlow,
427
+ applyEdgeChanges,
428
+ applyNodeChanges,
429
+ useReactFlow as useReactFlow2
430
+ } from "@xyflow/react";
431
+ import "@xyflow/react/dist/style.css";
432
+ import { PlusIcon as PlusIcon2, Trash2Icon } from "lucide-react";
433
+ import { useTranslations as useTranslations7 } from "next-intl";
434
+ import { useCallback as useCallback5, useEffect as useEffect4, useMemo as useMemo6, useRef as useRef2, useState as useState5 } from "react";
435
+ import { v4 as uuidv4 } from "uuid";
436
+
437
+ // src/features/email-automation/components/canvas/shared/CanvasToolbar.tsx
438
+ import { useReactFlow } from "@xyflow/react";
439
+ import { ListRestartIcon, MaximizeIcon, MinusIcon, PlusIcon } from "lucide-react";
440
+ import { useTranslations as useTranslations4 } from "next-intl";
441
+ import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
442
+ function CanvasToolbar({ onTidy }) {
443
+ const t = useTranslations4();
444
+ const { zoomIn, zoomOut, fitView } = useReactFlow();
445
+ return /* @__PURE__ */ jsxs3("div", { "data-help": "canvas.toolbar", className: "absolute end-2 top-2 z-10 flex flex-col gap-1 max-md:top-14", children: [
446
+ /* @__PURE__ */ jsx6(
447
+ Button,
448
+ {
449
+ type: "button",
450
+ variant: "outline",
451
+ size: "icon",
452
+ onClick: () => zoomIn(),
453
+ "aria-label": t("canvas.toolbar.zoomIn"),
454
+ children: /* @__PURE__ */ jsx6(PlusIcon, { className: "h-4 w-4" })
455
+ }
456
+ ),
457
+ /* @__PURE__ */ jsx6(
458
+ Button,
459
+ {
460
+ type: "button",
461
+ variant: "outline",
462
+ size: "icon",
463
+ onClick: () => zoomOut(),
464
+ "aria-label": t("canvas.toolbar.zoomOut"),
465
+ children: /* @__PURE__ */ jsx6(MinusIcon, { className: "h-4 w-4" })
466
+ }
467
+ ),
468
+ /* @__PURE__ */ jsx6(
469
+ Button,
470
+ {
471
+ type: "button",
472
+ variant: "outline",
473
+ size: "icon",
474
+ onClick: () => fitView({ duration: 300 }),
475
+ "aria-label": t("canvas.toolbar.fitView"),
476
+ children: /* @__PURE__ */ jsx6(MaximizeIcon, { className: "h-4 w-4" })
477
+ }
478
+ ),
479
+ /* @__PURE__ */ jsx6(
480
+ Button,
481
+ {
482
+ type: "button",
483
+ variant: "outline",
484
+ size: "icon",
485
+ onClick: () => void onTidy(),
486
+ title: t("canvas.toolbar.tidyTooltip"),
487
+ "aria-label": t("canvas.toolbar.tidy"),
488
+ children: /* @__PURE__ */ jsx6(ListRestartIcon, { className: "h-4 w-4" })
489
+ }
490
+ )
491
+ ] });
492
+ }
493
+ __name(CanvasToolbar, "CanvasToolbar");
494
+
495
+ // src/features/email-automation/components/canvas/shared/EmptyCanvas.tsx
496
+ import { jsx as jsx7 } from "react/jsx-runtime";
497
+ function EmptyCanvas({ children }) {
498
+ return /* @__PURE__ */ jsx7("div", { className: "text-muted-foreground flex h-full w-full items-center justify-center px-6 text-center text-sm", children });
499
+ }
500
+ __name(EmptyCanvas, "EmptyCanvas");
501
+
502
+ // src/features/email-automation/components/canvas/shared/autoLayout.ts
503
+ import dagre from "dagre";
504
+ function autoLayout(nodes, edges) {
505
+ const g = new dagre.graphlib.Graph();
506
+ g.setGraph({ rankdir: "LR", nodesep: 60, ranksep: 100, marginx: 24, marginy: 24 });
507
+ g.setDefaultEdgeLabel(() => ({}));
508
+ for (const n of nodes) {
509
+ g.setNode(n.id, { width: n.width, height: n.height });
510
+ }
511
+ for (const e of edges) {
512
+ if (g.hasNode(e.source) && g.hasNode(e.target)) {
513
+ g.setEdge(e.source, e.target);
514
+ }
515
+ }
516
+ dagre.layout(g);
517
+ const result = {};
518
+ for (const n of nodes) {
519
+ if (n.manuallyPositioned && typeof n.x === "number" && typeof n.y === "number") {
520
+ result[n.id] = { x: n.x, y: n.y };
521
+ } else {
522
+ const laid = g.node(n.id);
523
+ result[n.id] = {
524
+ x: laid.x - n.width / 2,
525
+ y: laid.y - n.height / 2
526
+ };
527
+ }
528
+ }
529
+ return result;
530
+ }
531
+ __name(autoLayout, "autoLayout");
532
+
533
+ // src/features/email-automation/components/canvas/shared/longPress.ts
534
+ var LONG_PRESS_MS = 500;
535
+ var LONG_PRESS_MOVE_TOLERANCE_PX = 10;
536
+ function createLongPressTracker(params) {
537
+ const delay = params.delayMs ?? LONG_PRESS_MS;
538
+ const tolerance = params.moveTolerancePx ?? LONG_PRESS_MOVE_TOLERANCE_PX;
539
+ const setTimer = params.setTimer ?? ((fn, ms) => setTimeout(fn, ms));
540
+ const clearTimer = params.clearTimer ?? ((handle) => clearTimeout(handle));
541
+ let timer = null;
542
+ let start = null;
543
+ let fired = false;
544
+ const cancel = /* @__PURE__ */ __name(() => {
545
+ if (timer !== null) clearTimer(timer);
546
+ timer = null;
547
+ start = null;
548
+ }, "cancel");
549
+ return {
550
+ down(target, clientX, clientY) {
551
+ cancel();
552
+ fired = false;
553
+ start = { x: clientX, y: clientY };
554
+ timer = setTimer(() => {
555
+ timer = null;
556
+ start = null;
557
+ fired = true;
558
+ params.onLongPress({ target, clientX, clientY });
559
+ }, delay);
560
+ },
561
+ move(clientX, clientY) {
562
+ if (!start) return;
563
+ if (Math.hypot(clientX - start.x, clientY - start.y) > tolerance) cancel();
564
+ },
565
+ up: cancel,
566
+ cancel,
567
+ consumeClick() {
568
+ const wasFired = fired;
569
+ fired = false;
570
+ return wasFired;
571
+ }
572
+ };
573
+ }
574
+ __name(createLongPressTracker, "createLongPressTracker");
575
+
576
+ // src/features/email-automation/components/canvas/buildCanvasGraph.ts
577
+ var TRIGGER_NODE_ID = "trigger";
578
+ var TRIGGER_SOURCE_HANDLE = "trigger-source";
579
+ var STEP_NODE_PREFIX = "step:";
580
+ var STEP_SOURCE_HANDLE_PREFIX = "step-source-";
581
+ var STEP_TARGET_HANDLE_PREFIX = "step-target-";
582
+ var TRIGGER_NODE_WIDTH = 200;
583
+ var TRIGGER_NODE_HEIGHT = 56;
584
+ var STEP_NODE_WIDTH = 200;
585
+ var STEP_NODE_HEIGHT = 72;
586
+ function findHeadStepId(steps) {
587
+ const pointedAt = new Set(steps.map((s) => s.next?.id).filter((id) => !!id));
588
+ return steps.find((s) => !pointedAt.has(s.id))?.id ?? null;
589
+ }
590
+ __name(findHeadStepId, "findHeadStepId");
591
+ var stepNodeId = /* @__PURE__ */ __name((stepId) => `${STEP_NODE_PREFIX}${stepId}`, "stepNodeId");
592
+ function buildCanvasGraph(steps, triggerLabel) {
593
+ const byId = new Map(steps.map((s) => [s.id, s]));
594
+ const headId = findHeadStepId(steps);
595
+ const chain = /* @__PURE__ */ new Set();
596
+ let cursor = headId ? byId.get(headId) : void 0;
597
+ while (cursor && !chain.has(cursor.id)) {
598
+ chain.add(cursor.id);
599
+ cursor = cursor.next ? byId.get(cursor.next.id) : void 0;
600
+ }
601
+ const orphanIds = steps.filter((s) => !chain.has(s.id)).map((s) => s.id);
602
+ const layoutNodes = [
603
+ { id: TRIGGER_NODE_ID, width: TRIGGER_NODE_WIDTH, height: TRIGGER_NODE_HEIGHT, manuallyPositioned: false },
604
+ ...steps.map((s) => ({
605
+ id: stepNodeId(s.id),
606
+ width: STEP_NODE_WIDTH,
607
+ height: STEP_NODE_HEIGHT,
608
+ manuallyPositioned: typeof s.positionX === "number" && typeof s.positionY === "number",
609
+ x: s.positionX,
610
+ y: s.positionY
611
+ }))
612
+ ];
613
+ const edges = [];
614
+ const layoutEdges = [];
615
+ const pushEdge = /* @__PURE__ */ __name((source, sourceHandle, target) => {
616
+ const data = { targetStepId: target.id, delayHours: target.delayHours };
617
+ edges.push({
618
+ id: `next:${source}->${target.id}`,
619
+ source,
620
+ sourceHandle,
621
+ target: stepNodeId(target.id),
622
+ targetHandle: `${STEP_TARGET_HANDLE_PREFIX}${target.id}`,
623
+ type: "delayEdge",
624
+ label: `${target.delayHours}h`,
625
+ data
626
+ });
627
+ layoutEdges.push({ source, target: stepNodeId(target.id) });
628
+ }, "pushEdge");
629
+ const head = headId ? byId.get(headId) : void 0;
630
+ if (head) pushEdge(TRIGGER_NODE_ID, TRIGGER_SOURCE_HANDLE, head);
631
+ for (const s of steps) {
632
+ const next = s.next ? byId.get(s.next.id) : void 0;
633
+ if (next) pushEdge(stepNodeId(s.id), `${STEP_SOURCE_HANDLE_PREFIX}${s.id}`, next);
634
+ }
635
+ const positions = autoLayout(layoutNodes, layoutEdges);
636
+ const nodes = [
637
+ {
638
+ id: TRIGGER_NODE_ID,
639
+ type: "triggerNode",
640
+ position: positions[TRIGGER_NODE_ID],
641
+ deletable: false,
642
+ data: { data: { label: triggerLabel } }
643
+ },
644
+ ...steps.map((s) => {
645
+ const id = stepNodeId(s.id);
646
+ return {
647
+ id,
648
+ type: "emailStepNode",
649
+ position: positions[id],
650
+ data: {
651
+ data: {
652
+ stepId: s.id,
653
+ subject: s.subject,
654
+ sentCount: s.sentCount,
655
+ orphan: !chain.has(s.id),
656
+ manuallyPositioned: typeof s.positionX === "number" && typeof s.positionY === "number"
657
+ }
658
+ }
659
+ };
660
+ })
661
+ ];
662
+ return { nodes, edges, orphanIds };
663
+ }
664
+ __name(buildCanvasGraph, "buildCanvasGraph");
665
+
666
+ // src/features/email-automation/components/canvas/edges/DelayEdge.tsx
667
+ import { BaseEdge, EdgeLabelRenderer, getBezierPath } from "@xyflow/react";
668
+ import { Fragment, jsx as jsx8, jsxs as jsxs4 } from "react/jsx-runtime";
669
+ function DelayEdge({
670
+ id,
671
+ sourceX,
672
+ sourceY,
673
+ targetX,
674
+ targetY,
675
+ sourcePosition,
676
+ targetPosition,
677
+ label,
678
+ data
679
+ }) {
680
+ const { select } = useEmailAutomation();
681
+ const [edgePath, labelX, labelY] = getBezierPath({
682
+ sourceX,
683
+ sourceY,
684
+ sourcePosition,
685
+ targetX,
686
+ targetY,
687
+ targetPosition
688
+ });
689
+ const targetStepId = data?.targetStepId;
690
+ return /* @__PURE__ */ jsxs4(Fragment, { children: [
691
+ /* @__PURE__ */ jsx8(BaseEdge, { id, path: edgePath, className: "stroke-foreground/60", interactionWidth: 24 }),
692
+ /* @__PURE__ */ jsx8(EdgeLabelRenderer, { children: /* @__PURE__ */ jsx8(
693
+ "div",
694
+ {
695
+ className: "nodrag nopan nowheel",
696
+ style: {
697
+ position: "absolute",
698
+ transform: `translate(-50%, -50%) translate(${labelX}px, ${labelY}px)`,
699
+ pointerEvents: "all",
700
+ zIndex: 1e3
701
+ },
702
+ onMouseDown: (e) => e.stopPropagation(),
703
+ children: /* @__PURE__ */ jsx8(
704
+ "button",
705
+ {
706
+ type: "button",
707
+ className: "nodrag nopan bg-background hover:bg-accent text-foreground border-border h-auto rounded border px-2 py-1 text-xs tabular-nums shadow-sm",
708
+ onClick: (e) => {
709
+ e.stopPropagation();
710
+ if (targetStepId) select(targetStepId);
711
+ },
712
+ children: label
713
+ }
714
+ )
715
+ }
716
+ ) })
717
+ ] });
718
+ }
719
+ __name(DelayEdge, "DelayEdge");
720
+
721
+ // src/features/email-automation/components/canvas/EmailAutomationCanvasInspector.tsx
722
+ import { useCallback as useCallback4, useMemo as useMemo5, useRef } from "react";
723
+
724
+ // src/features/email-automation/components/forms/EmailStepEditor.tsx
725
+ import { zodResolver as zodResolver2 } from "@hookform/resolvers/zod";
726
+ import { useTranslations as useTranslations5 } from "next-intl";
727
+ import { useCallback as useCallback3, useMemo as useMemo4 } from "react";
728
+ import { useForm as useForm2 } from "react-hook-form";
729
+ import { v4 as v43 } from "uuid";
730
+ import { z as z2 } from "zod";
731
+ import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
732
+ var TEMPLATE_VARIABLES = ["{{name}}", "{{email}}", "{{url}}", "{{unsubscribeUrl}}", "{{unsubscribeAllUrl}}"];
733
+ function EmailStepEditorInternal({
734
+ step,
735
+ automationId,
736
+ initialPosition,
737
+ isHead,
738
+ trigger,
739
+ forceShow,
740
+ onClose,
741
+ dialogOpen,
742
+ onDialogOpenChange,
743
+ onSuccess
744
+ }) {
745
+ const t = useTranslations5();
746
+ const { reload } = useEmailAutomation();
747
+ const isEdit = !!step;
748
+ const formSchema = useMemo4(
749
+ () => z2.object({
750
+ id: z2.uuidv4(),
751
+ subject: z2.string().min(1, { message: t("emailAutomations.step.fields.subject.error") }),
752
+ delayHours: z2.number().int().min(0),
753
+ body: z2.string().min(1, { message: t("emailAutomations.step.fields.body.error") })
754
+ }),
755
+ [t]
756
+ );
757
+ const getDefaultValues = useCallback3(
758
+ () => ({
759
+ id: step?.id ?? v43(),
760
+ subject: step?.subject ?? "",
761
+ delayHours: step?.delayHours ?? 24,
762
+ body: step?.body ?? ""
763
+ }),
764
+ [step]
765
+ );
766
+ const form = useForm2({
767
+ resolver: zodResolver2(formSchema),
768
+ defaultValues: getDefaultValues()
769
+ });
770
+ return /* @__PURE__ */ jsx9(
771
+ EditorSheet,
772
+ {
773
+ form,
774
+ entityType: t("emailAutomations.step.title"),
775
+ entityName: step?.subject,
776
+ title: isEdit ? t("emailAutomations.step.editTitle") : t("emailAutomations.step.title"),
777
+ isEdit,
778
+ module: Modules.EmailAutomationStep,
779
+ size: "lg",
780
+ onSubmit: async (values) => {
781
+ const payload = {
782
+ id: values.id,
783
+ automationId,
784
+ subject: values.subject,
785
+ body: values.body,
786
+ delayHours: values.delayHours,
787
+ positionX: step ? step.positionX : initialPosition?.x,
788
+ positionY: step ? step.positionY : initialPosition?.y
789
+ };
790
+ return isEdit ? await EmailAutomationStepService.update(payload) : await EmailAutomationStepService.create(payload);
791
+ },
792
+ onSuccess: async () => {
793
+ await reload();
794
+ await onSuccess?.();
795
+ },
796
+ onReset: getDefaultValues,
797
+ trigger,
798
+ forceShow,
799
+ onClose,
800
+ dialogOpen,
801
+ onDialogOpenChange,
802
+ children: /* @__PURE__ */ jsxs5("div", { className: "flex w-full flex-col gap-y-4", children: [
803
+ /* @__PURE__ */ jsx9(FormInput, { form, id: "subject", name: t("emailAutomations.step.fields.subject.label"), isRequired: true }),
804
+ /* @__PURE__ */ jsx9(
805
+ FormInput,
806
+ {
807
+ form,
808
+ id: "delayHours",
809
+ type: "number",
810
+ name: t("emailAutomations.step.fields.delayHours.label"),
811
+ description: isHead ? t("emailAutomations.canvas.hoursAfterEntering") : t("emailAutomations.canvas.hoursAfterPrevious")
812
+ }
813
+ ),
814
+ /* @__PURE__ */ jsx9(
815
+ FormTextarea,
816
+ {
817
+ form,
818
+ id: "body",
819
+ name: t("emailAutomations.step.fields.body.label"),
820
+ className: "min-h-72",
821
+ isRequired: true,
822
+ description: /* @__PURE__ */ jsxs5(Fragment2, { children: [
823
+ /* @__PURE__ */ jsxs5("span", { className: "block", children: [
824
+ t("emailAutomations.step.variablesHint"),
825
+ " ",
826
+ TEMPLATE_VARIABLES.map((variable) => /* @__PURE__ */ jsx9("span", { className: "me-2 text-foreground", children: variable }, variable))
827
+ ] }),
828
+ /* @__PURE__ */ jsx9("span", { className: "mt-1 block", children: t("emailAutomations.step.unsubscribeHint") })
829
+ ] })
830
+ }
831
+ )
832
+ ] })
833
+ }
834
+ );
835
+ }
836
+ __name(EmailStepEditorInternal, "EmailStepEditorInternal");
837
+ function EmailStepEditor(props) {
838
+ return /* @__PURE__ */ jsx9(EmailStepEditorInternal, { ...props });
839
+ }
840
+ __name(EmailStepEditor, "EmailStepEditor");
841
+
842
+ // src/features/email-automation/components/canvas/EmailAutomationCanvasInspector.tsx
843
+ import { jsx as jsx10 } from "react/jsx-runtime";
844
+ function EmailAutomationCanvasInspector() {
845
+ const { automation, steps, selectedStepId, select, reload } = useEmailAutomation();
846
+ const close = useCallback4(() => select(null), [select]);
847
+ const onSaved = useCallback4(async () => {
848
+ await reload();
849
+ }, [reload]);
850
+ const headStepId = useMemo5(() => findHeadStepId(steps), [steps]);
851
+ const step = useMemo5(() => steps.find((s) => s.id === selectedStepId) ?? null, [steps, selectedStepId]);
852
+ if (!automation || !selectedStepId || !step) return null;
853
+ return /* @__PURE__ */ jsx10(
854
+ StepEditor,
855
+ {
856
+ step,
857
+ automationId: automation.id,
858
+ isHead: step.id === headStepId,
859
+ onSaved,
860
+ onClose: close
861
+ },
862
+ step.id
863
+ );
864
+ }
865
+ __name(EmailAutomationCanvasInspector, "EmailAutomationCanvasInspector");
866
+ function useUserClosedGuard(onClose) {
867
+ const wasOpenRef = useRef(false);
868
+ return useCallback4(
869
+ (open) => {
870
+ if (open) {
871
+ wasOpenRef.current = true;
872
+ return;
873
+ }
874
+ if (wasOpenRef.current) onClose();
875
+ },
876
+ [onClose]
877
+ );
878
+ }
879
+ __name(useUserClosedGuard, "useUserClosedGuard");
880
+ function StepEditor(props) {
881
+ const onDialogOpenChange = useUserClosedGuard(props.onClose);
882
+ return /* @__PURE__ */ jsx10(
883
+ EmailStepEditor,
884
+ {
885
+ step: props.step,
886
+ automationId: props.automationId,
887
+ isHead: props.isHead,
888
+ forceShow: true,
889
+ dialogOpen: true,
890
+ onDialogOpenChange,
891
+ onSuccess: async () => {
892
+ await props.onSaved();
893
+ props.onClose();
894
+ }
895
+ }
896
+ );
897
+ }
898
+ __name(StepEditor, "StepEditor");
899
+
900
+ // src/features/email-automation/components/canvas/nodes/EmailStepNode.tsx
901
+ import { Handle, Position } from "@xyflow/react";
902
+ import { MailIcon as MailIcon2 } from "lucide-react";
903
+ import { useTranslations as useTranslations6 } from "next-intl";
904
+ import { jsx as jsx11, jsxs as jsxs6 } from "react/jsx-runtime";
905
+ function EmailStepNode({ data }) {
906
+ const d = data.data;
907
+ const t = useTranslations6();
908
+ const shellClass = d.orphan ? "border border-dashed border-foreground/30 bg-muted/40 opacity-60" : "border bg-background border-foreground/25 text-foreground";
909
+ return /* @__PURE__ */ jsxs6("div", { className: `w-[200px] cursor-pointer rounded-md p-2 shadow-sm ${shellClass}`, children: [
910
+ /* @__PURE__ */ jsx11(Handle, { type: "target", position: Position.Left, id: `${STEP_TARGET_HANDLE_PREFIX}${d.stepId}` }),
911
+ /* @__PURE__ */ jsx11(Handle, { type: "source", position: Position.Right, id: `${STEP_SOURCE_HANDLE_PREFIX}${d.stepId}` }),
912
+ /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-1 text-sm font-medium", children: [
913
+ /* @__PURE__ */ jsx11(MailIcon2, { className: "size-3 shrink-0" }),
914
+ /* @__PURE__ */ jsx11("span", { className: "truncate", children: d.subject })
915
+ ] }),
916
+ /* @__PURE__ */ jsx11("div", { className: "text-muted-foreground mt-1 text-xs tabular-nums", children: t("emailAutomations.canvas.sent", { count: d.sentCount }) }),
917
+ d.orphan && /* @__PURE__ */ jsx11("div", { className: "text-muted-foreground mt-1 text-xs italic", children: t("emailAutomations.canvas.orphan") })
918
+ ] });
919
+ }
920
+ __name(EmailStepNode, "EmailStepNode");
921
+
922
+ // src/features/email-automation/components/canvas/nodes/TriggerNode.tsx
923
+ import { Handle as Handle2, Position as Position2 } from "@xyflow/react";
924
+ import { ZapIcon } from "lucide-react";
925
+ import { jsx as jsx12, jsxs as jsxs7 } from "react/jsx-runtime";
926
+ function TriggerNode({ data }) {
927
+ return /* @__PURE__ */ jsxs7("div", { className: "bg-foreground text-background flex w-[200px] cursor-pointer items-center justify-center gap-1 rounded-full px-4 py-2 text-center shadow-md", children: [
928
+ /* @__PURE__ */ jsx12(ZapIcon, { className: "size-3 shrink-0 opacity-60" }),
929
+ /* @__PURE__ */ jsx12("div", { className: "truncate text-sm font-medium", children: data.data.label }),
930
+ /* @__PURE__ */ jsx12(Handle2, { type: "source", position: Position2.Right, id: TRIGGER_SOURCE_HANDLE })
931
+ ] });
932
+ }
933
+ __name(TriggerNode, "TriggerNode");
934
+
935
+ // src/features/email-automation/components/canvas/EmailAutomationCanvas.tsx
936
+ import { Fragment as Fragment3, jsx as jsx13, jsxs as jsxs8 } from "react/jsx-runtime";
937
+ var DEFAULT_DELAY_HOURS = 24;
938
+ var pointAnchor = /* @__PURE__ */ __name((screenX, screenY) => ({
939
+ getBoundingClientRect: /* @__PURE__ */ __name(() => ({
940
+ x: screenX,
941
+ y: screenY,
942
+ top: screenY,
943
+ left: screenX,
944
+ right: screenX,
945
+ bottom: screenY,
946
+ width: 0,
947
+ height: 0,
948
+ toJSON: /* @__PURE__ */ __name(() => ({}), "toJSON")
949
+ }), "getBoundingClientRect")
950
+ }), "pointAnchor");
951
+ var stepIdOf = /* @__PURE__ */ __name((nodeId) => nodeId?.startsWith(STEP_NODE_PREFIX) ? nodeId.slice(STEP_NODE_PREFIX.length) : null, "stepIdOf");
952
+ function EmailAutomationCanvas() {
953
+ const { automation, steps, triggers, select, reload, createStep, deleteStep, setNext, updatePosition, tidy } = useEmailAutomation();
954
+ const t = useTranslations7();
955
+ const isMobile = useIsMobile();
956
+ const [nodes, setNodes] = useState5([]);
957
+ const [edges, setEdges] = useState5([]);
958
+ const { screenToFlowPosition } = useReactFlow2();
959
+ const connectingFromRef = useRef2(null);
960
+ const [paneMenu, setPaneMenu] = useState5(null);
961
+ const [nodeMenu, setNodeMenu] = useState5(null);
962
+ const [pendingDeleteStepId, setPendingDeleteStepId] = useState5(null);
963
+ const [automationEditorOpen, setAutomationEditorOpen] = useState5(false);
964
+ const triggerLabel = useMemo6(
965
+ () => triggers.find((trigger) => trigger.key === automation?.trigger)?.label ?? t("emailAutomations.canvas.noTrigger"),
966
+ [triggers, automation?.trigger, t]
967
+ );
968
+ const { laidNodes, laidEdges } = useMemo6(() => {
969
+ const graph = buildCanvasGraph(steps, triggerLabel);
970
+ return { laidNodes: graph.nodes, laidEdges: graph.edges };
971
+ }, [steps, triggerLabel]);
972
+ useEffect4(() => {
973
+ setNodes(laidNodes);
974
+ setEdges(laidEdges);
975
+ }, [laidNodes, laidEdges]);
976
+ const onNodesChange = useCallback5((changes) => setNodes((nds) => applyNodeChanges(changes, nds)), []);
977
+ const onEdgesChange = useCallback5((changes) => setEdges((eds) => applyEdgeChanges(changes, eds)), []);
978
+ const onNodeDragStop = useCallback5(
979
+ (_e, node) => {
980
+ const stepId = stepIdOf(node.id);
981
+ if (!stepId) return;
982
+ void updatePosition(stepId, node.position.x, node.position.y);
983
+ },
984
+ [updatePosition]
985
+ );
986
+ const onConnectStart = useCallback5((_e, params) => {
987
+ connectingFromRef.current = { nodeId: params.nodeId ?? null, handleId: params.handleId ?? null };
988
+ }, []);
989
+ const linkSteps = useCallback5(
990
+ async (stepId, nextStepId) => {
991
+ try {
992
+ await setNext(stepId, nextStepId);
993
+ } catch (error) {
994
+ errorToast({ title: t("emailAutomations.canvas.chainError"), error });
995
+ await reload();
996
+ }
997
+ },
998
+ [setNext, reload, t]
999
+ );
1000
+ const onConnect = useCallback5(
1001
+ async (conn) => {
1002
+ const from = connectingFromRef.current;
1003
+ connectingFromRef.current = null;
1004
+ if (!from || !from.handleId) return;
1005
+ if (!conn.targetHandle?.startsWith(STEP_TARGET_HANDLE_PREFIX)) return;
1006
+ const targetStepId = conn.targetHandle.slice(STEP_TARGET_HANDLE_PREFIX.length);
1007
+ if (from.handleId === TRIGGER_SOURCE_HANDLE) {
1008
+ showError(t("emailAutomations.canvas.chainError"));
1009
+ return;
1010
+ }
1011
+ if (!from.handleId.startsWith(STEP_SOURCE_HANDLE_PREFIX)) return;
1012
+ const sourceStepId = from.handleId.slice(STEP_SOURCE_HANDLE_PREFIX.length);
1013
+ await linkSteps(sourceStepId, targetStepId);
1014
+ },
1015
+ [linkSteps, t]
1016
+ );
1017
+ const createStepAt = useCallback5(
1018
+ async (flowX, flowY) => {
1019
+ if (!automation) return null;
1020
+ const id = uuidv4();
1021
+ await createStep({
1022
+ id,
1023
+ automationId: automation.id,
1024
+ subject: t("emailAutomations.step.title"),
1025
+ body: "",
1026
+ delayHours: DEFAULT_DELAY_HOURS,
1027
+ positionX: flowX - STEP_NODE_WIDTH / 2,
1028
+ positionY: flowY - STEP_NODE_HEIGHT / 2
1029
+ });
1030
+ return id;
1031
+ },
1032
+ [automation, createStep, t]
1033
+ );
1034
+ const onConnectEnd = useCallback5(
1035
+ async (event, connectionState) => {
1036
+ const from = connectingFromRef.current;
1037
+ if (!from || !from.handleId || !automation) return;
1038
+ connectingFromRef.current = null;
1039
+ if (connectionState.toNode) return;
1040
+ const clientX = "clientX" in event ? event.clientX : event.changedTouches[0]?.clientX ?? 0;
1041
+ const clientY = "clientY" in event ? event.clientY : event.changedTouches[0]?.clientY ?? 0;
1042
+ const flowPos = screenToFlowPosition({ x: clientX, y: clientY });
1043
+ const sourceStepId = from.handleId.startsWith(STEP_SOURCE_HANDLE_PREFIX) ? from.handleId.slice(STEP_SOURCE_HANDLE_PREFIX.length) : null;
1044
+ if (!sourceStepId && from.handleId !== TRIGGER_SOURCE_HANDLE) return;
1045
+ const newStepId = await createStepAt(flowPos.x, flowPos.y);
1046
+ if (!newStepId) return;
1047
+ if (sourceStepId) await linkSteps(sourceStepId, newStepId);
1048
+ select(newStepId);
1049
+ },
1050
+ [automation, screenToFlowPosition, createStepAt, linkSteps, select]
1051
+ );
1052
+ const handlePaneContextMenu = useCallback5(
1053
+ (event) => {
1054
+ if (!automation) return;
1055
+ event.preventDefault();
1056
+ const clientX = event.clientX;
1057
+ const clientY = event.clientY;
1058
+ const flowPos = screenToFlowPosition({ x: clientX, y: clientY });
1059
+ setPaneMenu({
1060
+ flowX: flowPos.x,
1061
+ flowY: flowPos.y,
1062
+ screenX: clientX,
1063
+ screenY: clientY
1064
+ });
1065
+ },
1066
+ [automation, screenToFlowPosition]
1067
+ );
1068
+ const handleCreateStepFromPane = useCallback5(async () => {
1069
+ const menu = paneMenu;
1070
+ if (!menu) return;
1071
+ setPaneMenu(null);
1072
+ const newStepId = await createStepAt(menu.flowX, menu.flowY);
1073
+ if (newStepId) select(newStepId);
1074
+ }, [paneMenu, createStepAt, select]);
1075
+ const openNodeMenu = useCallback5((nodeId, screenX, screenY) => {
1076
+ const stepId = stepIdOf(nodeId);
1077
+ if (!stepId) return;
1078
+ setNodeMenu({ stepId, screenX, screenY });
1079
+ }, []);
1080
+ const handleNodeContextMenu = useCallback5(
1081
+ (event, node) => {
1082
+ event.preventDefault();
1083
+ openNodeMenu(node.id, event.clientX, event.clientY);
1084
+ },
1085
+ [openNodeMenu]
1086
+ );
1087
+ const openNodeMenuRef = useRef2(openNodeMenu);
1088
+ openNodeMenuRef.current = openNodeMenu;
1089
+ const longPress = useMemo6(
1090
+ () => createLongPressTracker({
1091
+ onLongPress: /* @__PURE__ */ __name(({ target, clientX, clientY }) => {
1092
+ openNodeMenuRef.current(target, clientX, clientY);
1093
+ }, "onLongPress")
1094
+ }),
1095
+ []
1096
+ );
1097
+ useEffect4(() => () => longPress.cancel(), [longPress]);
1098
+ const handleTouchPointerDown = useCallback5(
1099
+ (event) => {
1100
+ if (event.pointerType !== "touch") return;
1101
+ const el = event.target;
1102
+ if (!el || el.closest(".react-flow__handle")) return;
1103
+ const nodeId = el.closest(".react-flow__node")?.getAttribute("data-id");
1104
+ if (!stepIdOf(nodeId)) return;
1105
+ longPress.down(nodeId, event.clientX, event.clientY);
1106
+ },
1107
+ [longPress]
1108
+ );
1109
+ const handleNodeClick = useCallback5(
1110
+ (_event, node) => {
1111
+ if (node.id === TRIGGER_NODE_ID) {
1112
+ setAutomationEditorOpen(true);
1113
+ return;
1114
+ }
1115
+ const stepId = stepIdOf(node.id);
1116
+ if (stepId) select(stepId);
1117
+ },
1118
+ [select]
1119
+ );
1120
+ const handleDeleteStep = useCallback5(async () => {
1121
+ const stepId = pendingDeleteStepId;
1122
+ if (!stepId) return;
1123
+ try {
1124
+ await deleteStep(stepId);
1125
+ } catch (error) {
1126
+ errorToast({ title: t("emailAutomations.canvas.deleteEmail"), error });
1127
+ throw error;
1128
+ }
1129
+ }, [pendingDeleteStepId, deleteStep, t]);
1130
+ const nodeTypes = useMemo6(() => ({ triggerNode: TriggerNode, emailStepNode: EmailStepNode }), []);
1131
+ const edgeTypes = useMemo6(() => ({ delayEdge: DelayEdge }), []);
1132
+ return /* @__PURE__ */ jsxs8(Fragment3, { children: [
1133
+ /* @__PURE__ */ jsx13(
1134
+ ReactFlow,
1135
+ {
1136
+ nodes,
1137
+ edges,
1138
+ onNodesChange,
1139
+ onEdgesChange,
1140
+ onNodeDragStop,
1141
+ onConnectStart,
1142
+ onConnect,
1143
+ onConnectEnd,
1144
+ onPaneContextMenu: handlePaneContextMenu,
1145
+ onNodeContextMenu: handleNodeContextMenu,
1146
+ onNodeClick: handleNodeClick,
1147
+ nodeTypes,
1148
+ edgeTypes,
1149
+ onEdgeClick: (_event, edge) => {
1150
+ const targetStepId = edge.data?.targetStepId;
1151
+ if (targetStepId) select(targetStepId);
1152
+ },
1153
+ fitView: true,
1154
+ fitViewOptions: { maxZoom: 1 },
1155
+ minZoom: isMobile ? 0.15 : void 0,
1156
+ onPointerDownCapture: handleTouchPointerDown,
1157
+ onPointerMoveCapture: (event) => {
1158
+ if (event.pointerType === "touch") longPress.move(event.clientX, event.clientY);
1159
+ },
1160
+ onPointerUpCapture: () => longPress.up(),
1161
+ onPointerCancelCapture: () => longPress.cancel(),
1162
+ onClickCapture: (event) => {
1163
+ if (!longPress.consumeClick()) return;
1164
+ event.preventDefault();
1165
+ event.stopPropagation();
1166
+ },
1167
+ proOptions: { hideAttribution: true },
1168
+ children: /* @__PURE__ */ jsx13(Background, { gap: 16, size: 1 })
1169
+ }
1170
+ ),
1171
+ steps.length === 0 && // The canvas stays mounted under the empty state: the first email is
1172
+ // added by right-clicking it or dragging from the trigger.
1173
+ // Lower half only, so the text sits below the centred trigger node.
1174
+ /* @__PURE__ */ jsx13("div", { className: "pointer-events-none absolute inset-x-0 bottom-0 h-1/2", children: /* @__PURE__ */ jsx13(EmptyCanvas, { children: /* @__PURE__ */ jsxs8("div", { className: "flex flex-col gap-1", children: [
1175
+ /* @__PURE__ */ jsx13("span", { className: "font-medium", children: t("emailAutomations.canvas.emptyTitle") }),
1176
+ /* @__PURE__ */ jsx13("span", { className: "text-xs/relaxed", children: t("emailAutomations.canvas.emptyBody") })
1177
+ ] }) }) }),
1178
+ /* @__PURE__ */ jsx13(CanvasToolbar, { onTidy: tidy }),
1179
+ /* @__PURE__ */ jsx13(EmailAutomationCanvasInspector, {}),
1180
+ automationEditorOpen && automation && /* @__PURE__ */ jsx13(
1181
+ TriggerAutomationEditor,
1182
+ {
1183
+ automation,
1184
+ onSaved: reload,
1185
+ onClose: () => setAutomationEditorOpen(false)
1186
+ }
1187
+ ),
1188
+ /* @__PURE__ */ jsx13(
1189
+ ConfirmDialog,
1190
+ {
1191
+ open: pendingDeleteStepId !== null,
1192
+ onOpenChange: (open) => {
1193
+ if (!open) setPendingDeleteStepId(null);
1194
+ },
1195
+ title: t("emailAutomations.canvas.deleteEmail"),
1196
+ description: t("emailAutomations.canvas.deleteEmailConfirm"),
1197
+ confirmLabel: t("emailAutomations.canvas.deleteEmail"),
1198
+ cancelLabel: t("ui.buttons.cancel"),
1199
+ destructive: true,
1200
+ onConfirm: handleDeleteStep
1201
+ }
1202
+ ),
1203
+ paneMenu && /* @__PURE__ */ jsx13(
1204
+ ContextMenu,
1205
+ {
1206
+ open: true,
1207
+ onOpenChange: (open) => {
1208
+ if (!open) setPaneMenu(null);
1209
+ },
1210
+ children: /* @__PURE__ */ jsx13(
1211
+ ContextMenuContent,
1212
+ {
1213
+ anchor: pointAnchor(paneMenu.screenX, paneMenu.screenY),
1214
+ side: "bottom",
1215
+ align: "start",
1216
+ sideOffset: 4,
1217
+ children: /* @__PURE__ */ jsxs8(ContextMenuItem, { onClick: handleCreateStepFromPane, children: [
1218
+ /* @__PURE__ */ jsx13(PlusIcon2, {}),
1219
+ t("emailAutomations.canvas.addEmail")
1220
+ ] })
1221
+ }
1222
+ )
1223
+ }
1224
+ ),
1225
+ nodeMenu && /* @__PURE__ */ jsx13(
1226
+ ContextMenu,
1227
+ {
1228
+ open: true,
1229
+ onOpenChange: (open) => {
1230
+ if (!open) setNodeMenu(null);
1231
+ },
1232
+ children: /* @__PURE__ */ jsx13(
1233
+ ContextMenuContent,
1234
+ {
1235
+ anchor: pointAnchor(nodeMenu.screenX, nodeMenu.screenY),
1236
+ side: "bottom",
1237
+ align: "start",
1238
+ sideOffset: 4,
1239
+ children: /* @__PURE__ */ jsxs8(
1240
+ ContextMenuItem,
1241
+ {
1242
+ onClick: () => {
1243
+ const menu = nodeMenu;
1244
+ setNodeMenu(null);
1245
+ if (menu) setPendingDeleteStepId(menu.stepId);
1246
+ },
1247
+ children: [
1248
+ /* @__PURE__ */ jsx13(Trash2Icon, {}),
1249
+ t("emailAutomations.canvas.deleteEmail")
1250
+ ]
1251
+ }
1252
+ )
1253
+ }
1254
+ )
1255
+ }
1256
+ )
1257
+ ] });
1258
+ }
1259
+ __name(EmailAutomationCanvas, "EmailAutomationCanvas");
1260
+ function TriggerAutomationEditor(props) {
1261
+ const onDialogOpenChange = useUserClosedGuard(props.onClose);
1262
+ return /* @__PURE__ */ jsx13(
1263
+ EmailAutomationEditor,
1264
+ {
1265
+ automation: props.automation,
1266
+ forceShow: true,
1267
+ dialogOpen: true,
1268
+ onDialogOpenChange,
1269
+ onSuccess: async () => {
1270
+ await props.onSaved();
1271
+ props.onClose();
1272
+ }
1273
+ }
1274
+ );
1275
+ }
1276
+ __name(TriggerAutomationEditor, "TriggerAutomationEditor");
1277
+
1278
+ // src/features/email-automation/components/canvas/EmailAutomationCanvasTab.tsx
1279
+ import { jsx as jsx14 } from "react/jsx-runtime";
1280
+ function EmailAutomationCanvasTab() {
1281
+ const { open, setOpen } = useSidebar();
1282
+ useEffect5(() => {
1283
+ const prev = open;
1284
+ if (prev) setOpen(false);
1285
+ return () => {
1286
+ if (prev) setOpen(true);
1287
+ };
1288
+ }, []);
1289
+ return /* @__PURE__ */ jsx14("main", { className: "flex h-full w-full overflow-hidden", children: /* @__PURE__ */ jsx14("div", { className: "relative h-full min-w-0 flex-1", children: /* @__PURE__ */ jsx14(ReactFlowProvider, { children: /* @__PURE__ */ jsx14(EmailAutomationCanvas, {}) }) }) });
1290
+ }
1291
+ __name(EmailAutomationCanvasTab, "EmailAutomationCanvasTab");
1292
+
1293
+ // src/features/email-automation/components/lists/EmailAutomationRecipientsList.tsx
1294
+ import { RefreshCw } from "lucide-react";
1295
+ import { useTranslations as useTranslations8 } from "next-intl";
1296
+ import { useMemo as useMemo7, useState as useState6 } from "react";
1297
+ import { jsx as jsx15, jsxs as jsxs9 } from "react/jsx-runtime";
1298
+ function EmailAutomationRecipientsList() {
1299
+ const t = useTranslations8();
1300
+ const { automation } = useEmailAutomation();
1301
+ const [statusFilter, setStatusFilter] = useState6("all");
1302
+ const data = useDataListRetriever({
1303
+ retriever: /* @__PURE__ */ __name((params) => EmailAutomationService.findEnrollments(params), "retriever"),
1304
+ retrieverParams: { automationId: automation?.id },
1305
+ module: Modules.EmailAutomationEnrollment
1306
+ });
1307
+ const handleStatusChange = /* @__PURE__ */ __name((value) => {
1308
+ const status = value ?? "all";
1309
+ setStatusFilter(status);
1310
+ data.addAdditionalParameter("status", status === "all" ? null : status);
1311
+ }, "handleStatusChange");
1312
+ const statusItems = useMemo7(
1313
+ () => ({
1314
+ all: t("emailAutomations.recipients.status.all"),
1315
+ active: t("emailAutomations.recipients.status.active"),
1316
+ completed: t("emailAutomations.recipients.status.completed"),
1317
+ unsubscribed: t("emailAutomations.recipients.status.unsubscribed"),
1318
+ exited: t("emailAutomations.recipients.status.exited"),
1319
+ failed: t("emailAutomations.recipients.status.failed")
1320
+ }),
1321
+ [t]
1322
+ );
1323
+ const filters = /* @__PURE__ */ jsxs9("div", { className: "flex items-center gap-4", children: [
1324
+ /* @__PURE__ */ jsxs9(Select, { items: statusItems, value: statusFilter, onValueChange: handleStatusChange, children: [
1325
+ /* @__PURE__ */ jsx15(SelectTrigger, { className: "w-40", children: /* @__PURE__ */ jsx15(SelectValue, {}) }),
1326
+ /* @__PURE__ */ jsxs9(SelectContent, { children: [
1327
+ /* @__PURE__ */ jsx15(SelectItem, { value: "all", children: t("emailAutomations.recipients.status.all") }),
1328
+ /* @__PURE__ */ jsx15(SelectItem, { value: "active", children: t("emailAutomations.recipients.status.active") }),
1329
+ /* @__PURE__ */ jsx15(SelectItem, { value: "completed", children: t("emailAutomations.recipients.status.completed") }),
1330
+ /* @__PURE__ */ jsx15(SelectItem, { value: "unsubscribed", children: t("emailAutomations.recipients.status.unsubscribed") }),
1331
+ /* @__PURE__ */ jsx15(SelectItem, { value: "exited", children: t("emailAutomations.recipients.status.exited") }),
1332
+ /* @__PURE__ */ jsx15(SelectItem, { value: "failed", children: t("emailAutomations.recipients.status.failed") })
1333
+ ] })
1334
+ ] }),
1335
+ /* @__PURE__ */ jsx15(Button, { variant: "outline", size: "icon", onClick: () => void data.refresh(), disabled: !data.isLoaded, children: /* @__PURE__ */ jsx15(RefreshCw, { className: `h-4 w-4 ${!data.isLoaded ? "animate-spin" : ""}` }) })
1336
+ ] });
1337
+ return /* @__PURE__ */ jsx15(
1338
+ ContentListTable,
1339
+ {
1340
+ data,
1341
+ fields: [
1342
+ "email" /* email */,
1343
+ "name" /* name */,
1344
+ "status" /* status */,
1345
+ "currentStep" /* currentStep */,
1346
+ "nextSendAt" /* nextSendAt */,
1347
+ "sentCount" /* sentCount */
1348
+ ],
1349
+ tableGeneratorType: Modules.EmailAutomationEnrollment,
1350
+ filters,
1351
+ allowSearch: false,
1352
+ emptyState: t("emailAutomations.recipients.empty")
1353
+ }
1354
+ );
1355
+ }
1356
+ __name(EmailAutomationRecipientsList, "EmailAutomationRecipientsList");
1357
+
1358
+ // src/features/email-automation/components/containers/EmailAutomationContainer.tsx
1359
+ import { jsx as jsx16 } from "react/jsx-runtime";
1360
+ function EmailAutomationContainer({ automationId }) {
1361
+ return /* @__PURE__ */ jsx16(EmailAutomationProvider, { automationId, children: /* @__PURE__ */ jsx16(EmailAutomationShell, {}) });
1362
+ }
1363
+ __name(EmailAutomationContainer, "EmailAutomationContainer");
1364
+ function EmailAutomationShell() {
1365
+ const t = useTranslations9();
1366
+ const generateUrl = usePageUrlGenerator();
1367
+ const { automation, reload } = useEmailAutomation();
1368
+ if (!automation) return null;
1369
+ const breadcrumbs = [
1370
+ { name: t("emailAutomations.admin.title"), href: generateUrl({ page: Modules.EmailAutomation }) },
1371
+ { name: automation.name }
1372
+ ];
1373
+ return /* @__PURE__ */ jsx16(
1374
+ SharedProvider,
1375
+ {
1376
+ value: {
1377
+ breadcrumbs,
1378
+ title: {
1379
+ type: t("emailAutomations.admin.title"),
1380
+ element: automation.name,
1381
+ functions: [
1382
+ /* @__PURE__ */ jsx16(EmailAutomationActiveSwitch, {}, "emailAutomationActiveSwitch"),
1383
+ /* @__PURE__ */ jsx16(EmailAutomationEditor, { automation, onSuccess: reload }, "emailAutomationEditor")
1384
+ ]
1385
+ }
1386
+ },
1387
+ children: /* @__PURE__ */ jsx16(
1388
+ RoundPageContainer,
1389
+ {
1390
+ module: Modules.EmailAutomation,
1391
+ id: automation.id,
1392
+ fullWidth: true,
1393
+ forceHeader: true,
1394
+ scroll: "fixed",
1395
+ tabs: [
1396
+ {
1397
+ sectionKey: "canvas",
1398
+ label: t("emailAutomations.tabs.canvas"),
1399
+ content: /* @__PURE__ */ jsx16(EmailAutomationCanvasTab, {}),
1400
+ fillHeight: true
1401
+ },
1402
+ {
1403
+ sectionKey: "recipients",
1404
+ label: t("emailAutomations.tabs.recipients"),
1405
+ content: /* @__PURE__ */ jsx16(EmailAutomationRecipientsList, {})
1406
+ }
1407
+ ]
1408
+ }
1409
+ )
1410
+ }
1411
+ );
1412
+ }
1413
+ __name(EmailAutomationShell, "EmailAutomationShell");
1414
+
1415
+ // src/features/email-automation/components/unsubscribe/EmailUnsubscribe.tsx
1416
+ import { CheckCircle, Loader2, XCircle } from "lucide-react";
1417
+ import { useTranslations as useTranslations10 } from "next-intl";
1418
+ import { useEffect as useEffect6, useState as useState7 } from "react";
1419
+ import { Fragment as Fragment4, jsx as jsx17, jsxs as jsxs10 } from "react/jsx-runtime";
1420
+ function EmailUnsubscribe({ token, scope }) {
1421
+ const t = useTranslations10();
1422
+ const [state, setState] = useState7("loading");
1423
+ const [stoppedScope, setStoppedScope] = useState7(scope);
1424
+ const [automationName, setAutomationName] = useState7("");
1425
+ const [isStoppingAll, setIsStoppingAll] = useState7(false);
1426
+ useEffect6(() => {
1427
+ async function unsubscribe() {
1428
+ try {
1429
+ const enrollment = await EmailAutomationEnrollmentService.unsubscribe({ token, scope });
1430
+ setAutomationName(enrollment?.automation?.name ?? "");
1431
+ setStoppedScope(scope);
1432
+ setState("success");
1433
+ } catch {
1434
+ setState("error");
1435
+ }
1436
+ }
1437
+ __name(unsubscribe, "unsubscribe");
1438
+ unsubscribe();
1439
+ }, [token, scope]);
1440
+ const stopAll = /* @__PURE__ */ __name(async () => {
1441
+ setIsStoppingAll(true);
1442
+ try {
1443
+ await EmailAutomationEnrollmentService.unsubscribe({ token, scope: "all" });
1444
+ setStoppedScope("all");
1445
+ } catch {
1446
+ setState("error");
1447
+ } finally {
1448
+ setIsStoppingAll(false);
1449
+ }
1450
+ }, "stopAll");
1451
+ if (state === "loading") {
1452
+ return /* @__PURE__ */ jsxs10("div", { className: "flex flex-col items-center justify-center space-y-4 py-16 text-center", children: [
1453
+ /* @__PURE__ */ jsx17(Loader2, { className: "text-primary h-12 w-12 animate-spin" }),
1454
+ /* @__PURE__ */ jsx17("p", { className: "text-muted-foreground text-sm", children: t("emailAutomations.unsubscribe.loading") })
1455
+ ] });
1456
+ }
1457
+ if (state === "error") {
1458
+ return /* @__PURE__ */ jsxs10("div", { className: "flex flex-col items-center justify-center space-y-4 py-16 text-center", children: [
1459
+ /* @__PURE__ */ jsx17("div", { className: "bg-destructive/10 rounded-full p-4", children: /* @__PURE__ */ jsx17(XCircle, { className: "text-destructive h-12 w-12" }) }),
1460
+ /* @__PURE__ */ jsx17(SectionHeader, { level: 2, children: t("emailAutomations.unsubscribe.invalidTitle") }),
1461
+ /* @__PURE__ */ jsx17("p", { className: "text-muted-foreground max-w-md text-sm leading-relaxed", children: t("emailAutomations.unsubscribe.invalidBody") })
1462
+ ] });
1463
+ }
1464
+ return /* @__PURE__ */ jsxs10("div", { className: "flex flex-col items-center justify-center space-y-4 py-16 text-center", children: [
1465
+ /* @__PURE__ */ jsx17("div", { className: "bg-primary/10 rounded-full p-4", children: /* @__PURE__ */ jsx17(CheckCircle, { className: "text-primary h-12 w-12" }) }),
1466
+ /* @__PURE__ */ jsx17(SectionHeader, { level: 2, children: t("emailAutomations.unsubscribe.title") }),
1467
+ stoppedScope === "all" ? /* @__PURE__ */ jsx17("p", { className: "text-muted-foreground max-w-md text-sm leading-relaxed", children: t("emailAutomations.unsubscribe.stoppedAll") }) : /* @__PURE__ */ jsxs10(Fragment4, { children: [
1468
+ /* @__PURE__ */ jsx17("p", { className: "text-muted-foreground max-w-md text-sm leading-relaxed", children: t("emailAutomations.unsubscribe.stoppedAutomation", { name: automationName }) }),
1469
+ /* @__PURE__ */ jsxs10(Button, { variant: "secondary", onClick: stopAll, disabled: isStoppingAll, children: [
1470
+ isStoppingAll && /* @__PURE__ */ jsx17(Loader2, { className: "h-4 w-4 animate-spin" }),
1471
+ t("emailAutomations.unsubscribe.alsoStopAll")
1472
+ ] })
1473
+ ] })
1474
+ ] });
1475
+ }
1476
+ __name(EmailUnsubscribe, "EmailUnsubscribe");
1477
+
1478
+ // src/features/email-automation/hooks/useEmailAutomationTableStructure.tsx
1479
+ import { useTranslations as useTranslations11 } from "next-intl";
1480
+ import { useMemo as useMemo8 } from "react";
1481
+ import { jsx as jsx18 } from "react/jsx-runtime";
1482
+ var useEmailAutomationTableStructure = /* @__PURE__ */ __name((params) => {
1483
+ const t = useTranslations11();
1484
+ const generateUrl = usePageUrlGenerator();
1485
+ const triggerLabels = params.context?.triggerLabels ?? {};
1486
+ const tableData = useMemo8(() => {
1487
+ return params.data.map((automation) => {
1488
+ const entry = { jsonApiData: automation };
1489
+ params.fields.forEach((field) => {
1490
+ entry[field] = automation[field];
1491
+ });
1492
+ return entry;
1493
+ });
1494
+ }, [params.data, params.fields]);
1495
+ const numericColumn = /* @__PURE__ */ __name((field, header, read) => ({
1496
+ id: field,
1497
+ accessorKey: field,
1498
+ header,
1499
+ cell: /* @__PURE__ */ __name(({ row }) => {
1500
+ const automation = row.original.jsonApiData;
1501
+ return /* @__PURE__ */ jsx18("span", { className: "block text-xs tabular-nums text-end", children: read(automation) ?? 0 });
1502
+ }, "cell"),
1503
+ enableSorting: false,
1504
+ enableHiding: false
1505
+ }), "numericColumn");
1506
+ const fieldColumnMap = {
1507
+ ["name" /* name */]: () => ({
1508
+ id: "name",
1509
+ accessorKey: "name",
1510
+ header: t("emailAutomations.list.columns.name"),
1511
+ cell: /* @__PURE__ */ __name(({ row }) => {
1512
+ const automation = row.original.jsonApiData;
1513
+ return /* @__PURE__ */ jsx18(Link, { href: generateUrl({ page: Modules.EmailAutomation, id: automation.id }), children: automation.name });
1514
+ }, "cell"),
1515
+ enableSorting: false,
1516
+ enableHiding: false
1517
+ }),
1518
+ ["trigger" /* trigger */]: () => ({
1519
+ id: "trigger",
1520
+ accessorKey: "trigger",
1521
+ header: t("emailAutomations.list.columns.trigger"),
1522
+ cell: /* @__PURE__ */ __name(({ row }) => {
1523
+ const automation = row.original.jsonApiData;
1524
+ if (!automation.trigger) return /* @__PURE__ */ jsx18("span", { className: "text-muted-foreground text-xs", children: "\u2014" });
1525
+ return /* @__PURE__ */ jsx18("span", { className: "text-sm", children: triggerLabels[automation.trigger] ?? automation.trigger });
1526
+ }, "cell"),
1527
+ enableSorting: false,
1528
+ enableHiding: false
1529
+ }),
1530
+ ["status" /* status */]: () => ({
1531
+ id: "status",
1532
+ accessorKey: "status",
1533
+ header: t("emailAutomations.list.columns.status"),
1534
+ cell: /* @__PURE__ */ __name(({ row }) => {
1535
+ const automation = row.original.jsonApiData;
1536
+ return automation.isActive ? /* @__PURE__ */ jsx18(Badge, { variant: "softGreen", children: t("emailAutomations.list.status.on") }) : /* @__PURE__ */ jsx18(Badge, { variant: "softGray", children: t("emailAutomations.list.status.off") });
1537
+ }, "cell"),
1538
+ enableSorting: false,
1539
+ enableHiding: false
1540
+ }),
1541
+ ["steps" /* steps */]: () => numericColumn("steps" /* steps */, t("emailAutomations.list.columns.steps"), (a) => a.stepCount),
1542
+ ["active" /* active */]: () => numericColumn("active" /* active */, t("emailAutomations.list.columns.active"), (a) => a.activeCount),
1543
+ ["completed" /* completed */]: () => numericColumn(
1544
+ "completed" /* completed */,
1545
+ t("emailAutomations.list.columns.completed"),
1546
+ (a) => a.completedCount
1547
+ ),
1548
+ ["unsubscribed" /* unsubscribed */]: () => numericColumn(
1549
+ "unsubscribed" /* unsubscribed */,
1550
+ t("emailAutomations.list.columns.unsubscribed"),
1551
+ (a) => a.unsubscribedCount
1552
+ )
1553
+ };
1554
+ const columns = useMemo8(() => {
1555
+ return params.fields.map((field) => fieldColumnMap[field]?.()).filter((col) => col !== void 0);
1556
+ }, [params.fields, fieldColumnMap, t, generateUrl]);
1557
+ return useMemo8(() => ({ data: tableData, columns }), [tableData, columns]);
1558
+ }, "useEmailAutomationTableStructure");
1559
+ registerTableGenerator("email-automations", useEmailAutomationTableStructure);
1560
+
1561
+ // src/features/email-automation/hooks/useEmailAutomationEnrollmentTableStructure.tsx
1562
+ import { useLocale, useTranslations as useTranslations12 } from "next-intl";
1563
+ import { useMemo as useMemo9 } from "react";
1564
+ import { jsx as jsx19 } from "react/jsx-runtime";
1565
+ var STATUS_VARIANTS = {
1566
+ active: "softGreen",
1567
+ completed: "softBlue",
1568
+ unsubscribed: "softGray",
1569
+ exited: "softGray",
1570
+ failed: "softRed"
1571
+ };
1572
+ var useEmailAutomationEnrollmentTableStructure = /* @__PURE__ */ __name((params) => {
1573
+ const t = useTranslations12();
1574
+ const locale = dateFormatLocale(useLocale());
1575
+ const tableData = useMemo9(() => {
1576
+ return params.data.map((enrollment) => {
1577
+ const entry = { jsonApiData: enrollment };
1578
+ params.fields.forEach((field) => {
1579
+ entry[field] = enrollment[field];
1580
+ });
1581
+ return entry;
1582
+ });
1583
+ }, [params.data, params.fields]);
1584
+ const empty = /* @__PURE__ */ jsx19("span", { className: "text-muted-foreground text-xs", children: "\u2014" });
1585
+ const fieldColumnMap = {
1586
+ ["email" /* email */]: () => ({
1587
+ id: "email",
1588
+ accessorKey: "email",
1589
+ header: t("emailAutomations.recipients.columns.email"),
1590
+ cell: /* @__PURE__ */ __name(({ row }) => {
1591
+ const enrollment = row.original.jsonApiData;
1592
+ return /* @__PURE__ */ jsx19("span", { className: "text-sm", children: enrollment.email });
1593
+ }, "cell"),
1594
+ enableSorting: false,
1595
+ enableHiding: false
1596
+ }),
1597
+ ["name" /* name */]: () => ({
1598
+ id: "name",
1599
+ accessorKey: "name",
1600
+ header: t("emailAutomations.recipients.columns.name"),
1601
+ cell: /* @__PURE__ */ __name(({ row }) => {
1602
+ const enrollment = row.original.jsonApiData;
1603
+ return enrollment.name ? /* @__PURE__ */ jsx19("span", { className: "text-sm", children: enrollment.name }) : empty;
1604
+ }, "cell"),
1605
+ enableSorting: false,
1606
+ enableHiding: false
1607
+ }),
1608
+ ["status" /* status */]: () => ({
1609
+ id: "status",
1610
+ accessorKey: "status",
1611
+ header: t("emailAutomations.recipients.columns.status"),
1612
+ cell: /* @__PURE__ */ __name(({ row }) => {
1613
+ const enrollment = row.original.jsonApiData;
1614
+ return /* @__PURE__ */ jsx19(
1615
+ Badge,
1616
+ {
1617
+ variant: STATUS_VARIANTS[enrollment.status] ?? "softGray",
1618
+ title: enrollment.status === "failed" ? enrollment.lastError : void 0,
1619
+ children: t(`emailAutomations.recipients.status.${enrollment.status}`)
1620
+ }
1621
+ );
1622
+ }, "cell"),
1623
+ enableSorting: false,
1624
+ enableHiding: false
1625
+ }),
1626
+ ["currentStep" /* currentStep */]: () => ({
1627
+ id: "currentStep",
1628
+ accessorKey: "currentStep",
1629
+ header: t("emailAutomations.recipients.columns.currentStep"),
1630
+ cell: /* @__PURE__ */ __name(({ row }) => {
1631
+ const enrollment = row.original.jsonApiData;
1632
+ return enrollment.currentStepSubject ? /* @__PURE__ */ jsx19("span", { className: "text-sm", children: enrollment.currentStepSubject }) : empty;
1633
+ }, "cell"),
1634
+ enableSorting: false,
1635
+ enableHiding: false
1636
+ }),
1637
+ ["nextSendAt" /* nextSendAt */]: () => ({
1638
+ id: "nextSendAt",
1639
+ accessorKey: "nextSendAt",
1640
+ header: t("emailAutomations.recipients.columns.nextSendAt"),
1641
+ cell: /* @__PURE__ */ __name(({ row }) => {
1642
+ const enrollment = row.original.jsonApiData;
1643
+ if (!enrollment.nextSendAt) return empty;
1644
+ return /* @__PURE__ */ jsx19("span", { className: "text-xs tabular-nums", children: formatDate(enrollment.nextSendAt, "dateTime", locale) });
1645
+ }, "cell"),
1646
+ enableSorting: false,
1647
+ enableHiding: false
1648
+ }),
1649
+ ["sentCount" /* sentCount */]: () => ({
1650
+ id: "sentCount",
1651
+ accessorKey: "sentCount",
1652
+ header: t("emailAutomations.recipients.columns.sentCount"),
1653
+ cell: /* @__PURE__ */ __name(({ row }) => {
1654
+ const enrollment = row.original.jsonApiData;
1655
+ return /* @__PURE__ */ jsx19("span", { className: "block text-xs tabular-nums text-end", children: enrollment.sentCount ?? 0 });
1656
+ }, "cell"),
1657
+ enableSorting: false,
1658
+ enableHiding: false
1659
+ })
1660
+ };
1661
+ const columns = useMemo9(() => {
1662
+ return params.fields.map((field) => fieldColumnMap[field]?.()).filter((col) => col !== void 0);
1663
+ }, [params.fields, fieldColumnMap, t, locale]);
1664
+ return useMemo9(() => ({ data: tableData, columns }), [tableData, columns]);
1665
+ }, "useEmailAutomationEnrollmentTableStructure");
1666
+ registerTableGenerator("email-automation-enrollments", useEmailAutomationEnrollmentTableStructure);
1667
+ export {
1668
+ CanvasToolbar,
1669
+ DelayEdge,
1670
+ EmailAutomationActiveSwitch,
1671
+ EmailAutomationCanvas,
1672
+ EmailAutomationCanvasInspector,
1673
+ EmailAutomationCanvasTab,
1674
+ EmailAutomationContainer,
1675
+ EmailAutomationEditor,
1676
+ EmailAutomationProvider,
1677
+ EmailAutomationRecipientsList,
1678
+ EmailAutomationsList,
1679
+ EmailAutomationsListContainer,
1680
+ EmailStepEditor,
1681
+ EmailStepNode,
1682
+ EmailUnsubscribe,
1683
+ EmptyCanvas,
1684
+ LONG_PRESS_MOVE_TOLERANCE_PX,
1685
+ LONG_PRESS_MS,
1686
+ STEP_NODE_HEIGHT,
1687
+ STEP_NODE_PREFIX,
1688
+ STEP_NODE_WIDTH,
1689
+ STEP_SOURCE_HANDLE_PREFIX,
1690
+ STEP_TARGET_HANDLE_PREFIX,
1691
+ TRIGGER_NODE_HEIGHT,
1692
+ TRIGGER_NODE_ID,
1693
+ TRIGGER_NODE_WIDTH,
1694
+ TRIGGER_SOURCE_HANDLE,
1695
+ TriggerNode,
1696
+ autoLayout,
1697
+ buildCanvasGraph,
1698
+ createLongPressTracker,
1699
+ findHeadStepId,
1700
+ useEmailAutomation,
1701
+ useEmailAutomationEnrollmentTableStructure,
1702
+ useEmailAutomationTableStructure,
1703
+ useUserClosedGuard
1704
+ };
1705
+ //# sourceMappingURL=index.mjs.map