@carlonicora/nextjs-jsonapi 5.1.3 → 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.
- package/dist/{BlockNoteEditor-KO6YFNIU.js → BlockNoteEditor-UJ6363LC.js} +19 -19
- package/dist/{BlockNoteEditor-KO6YFNIU.js.map → BlockNoteEditor-UJ6363LC.js.map} +1 -1
- package/dist/{BlockNoteEditor-66FJUDL4.mjs → BlockNoteEditor-WTSQANKI.mjs} +4 -4
- package/dist/{BlockNoteViewer-LK22AE7Z.js → BlockNoteViewer-EL4JFB6P.js} +3 -3
- package/dist/{BlockNoteViewer-LK22AE7Z.js.map → BlockNoteViewer-EL4JFB6P.js.map} +1 -1
- package/dist/{BlockNoteViewer-SUK7OMWH.mjs → BlockNoteViewer-JVKWJZ6J.mjs} +2 -2
- package/dist/auth.interface-CSzJ1oP1.d.ts +64 -0
- package/dist/auth.interface-DD4uQv2K.d.mts +64 -0
- package/dist/billing/index.js +441 -441
- package/dist/billing/index.mjs +3 -3
- package/dist/{chunk-SZQNUGDG.js → chunk-3BAM23VA.js} +1005 -929
- package/dist/chunk-3BAM23VA.js.map +1 -0
- package/dist/{chunk-OKUGTXDN.mjs → chunk-6WU74VFU.mjs} +121 -3
- package/dist/chunk-6WU74VFU.mjs.map +1 -0
- package/dist/{chunk-VVJDWQEI.js → chunk-CRI4T47Y.js} +790 -179
- package/dist/chunk-CRI4T47Y.js.map +1 -0
- package/dist/{chunk-TO6IYGML.js → chunk-EO75CV76.js} +126 -8
- package/dist/chunk-EO75CV76.js.map +1 -0
- package/dist/{chunk-KL7CAQP3.mjs → chunk-FNTQQFLH.mjs} +2 -2
- package/dist/{chunk-OCJEXAGE.mjs → chunk-JQWI62NV.mjs} +617 -6
- package/dist/chunk-JQWI62NV.mjs.map +1 -0
- package/dist/{chunk-5JADF7ZA.mjs → chunk-MIZTWBPX.mjs} +171 -95
- package/dist/chunk-MIZTWBPX.mjs.map +1 -0
- package/dist/{chunk-XLVRAHUK.mjs → chunk-QKZUE5OF.mjs} +2 -2
- package/dist/{chunk-67NJY7SM.js → chunk-QQQE3N2X.js} +3 -3
- package/dist/{chunk-67NJY7SM.js.map → chunk-QQQE3N2X.js.map} +1 -1
- package/dist/{chunk-XCHXOCEJ.js → chunk-XGTQWBDJ.js} +4 -4
- package/dist/{chunk-XCHXOCEJ.js.map → chunk-XGTQWBDJ.js.map} +1 -1
- package/dist/client/index.d.mts +6 -39
- package/dist/client/index.d.ts +6 -39
- package/dist/client/index.js +4 -4
- package/dist/client/index.mjs +3 -3
- package/dist/components/index.d.mts +19 -5
- package/dist/components/index.d.ts +19 -5
- package/dist/components/index.js +6 -4
- package/dist/components/index.js.map +1 -1
- package/dist/components/index.mjs +5 -3
- package/dist/contexts/index.js +4 -4
- package/dist/contexts/index.mjs +3 -3
- package/dist/core/index.d.mts +214 -8
- package/dist/core/index.d.ts +214 -8
- package/dist/core/index.js +30 -2
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +29 -1
- package/dist/email-automation-enrollment.fields-BZvF14qx.d.mts +82 -0
- package/dist/email-automation-enrollment.fields-Cp3VMzFq.d.ts +82 -0
- package/dist/email-automations/index.css +30 -0
- package/dist/email-automations/index.css.map +1 -0
- package/dist/email-automations/index.d.mts +303 -0
- package/dist/email-automations/index.d.ts +303 -0
- package/dist/email-automations/index.js +1705 -0
- package/dist/email-automations/index.js.map +1 -0
- package/dist/email-automations/index.mjs +1705 -0
- package/dist/email-automations/index.mjs.map +1 -0
- package/dist/features/analytics/index.js +122 -122
- package/dist/features/analytics/index.mjs +4 -4
- package/dist/features/analytics/server.js +4 -4
- package/dist/features/analytics/server.mjs +2 -2
- package/dist/features/help/index.js +37 -37
- package/dist/features/help/index.mjs +3 -3
- package/dist/features/tokenusage/index.js +125 -125
- package/dist/features/tokenusage/index.mjs +4 -4
- package/dist/index.d.mts +5 -4
- package/dist/index.d.ts +5 -4
- package/dist/index.js +41 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +41 -3
- package/dist/{s3.service-C-4YpEMu.d.ts → s3.service-CY-jgOAM.d.ts} +1 -1
- package/dist/{s3.service-dVj-nxMl.d.mts → s3.service-Sri0QP7Z.d.mts} +1 -1
- package/dist/server/index.d.mts +2 -2
- package/dist/server/index.d.ts +2 -2
- package/dist/server/index.js +3 -3
- package/dist/server/index.mjs +2 -2
- package/dist/types-70acd-8C.d.mts +39 -0
- package/dist/types-l8SFleXi.d.ts +39 -0
- package/dist/{waitlist.config-kPfjImle.d.mts → waitlist.config-Cagmgm7-.d.mts} +7 -0
- package/dist/{waitlist.config-kPfjImle.d.ts → waitlist.config-Cagmgm7-.d.ts} +7 -0
- package/messages/en.json +132 -1
- package/messages/it.json +132 -1
- package/package.json +11 -1
- package/scripts/validate-messages.mjs +1 -0
- package/src/components/forms/FormFieldWrapper.tsx +1 -1
- package/src/components/forms/FormTextarea.tsx +2 -1
- package/src/components/index.ts +1 -0
- package/src/core/index.ts +8 -0
- package/src/core/registry/ModuleRegistry.ts +5 -0
- package/src/email-automations/index.ts +5 -0
- package/src/features/auth/components/buttons/GoogleSignInButton.tsx +8 -4
- package/src/features/auth/components/forms/Register.tsx +20 -8
- package/src/features/auth/data/auth.interface.ts +2 -0
- package/src/features/auth/data/auth.ts +10 -0
- package/src/features/email-automation/EmailAutomationEnrollmentModule.ts +11 -0
- package/src/features/email-automation/EmailAutomationModule.ts +30 -0
- package/src/features/email-automation/EmailAutomationStepModule.ts +11 -0
- package/src/features/email-automation/EmailAutomationTriggerModule.ts +11 -0
- package/src/features/email-automation/components/__tests__/EmailAutomationEditor.spec.tsx +104 -0
- package/src/features/email-automation/components/canvas/EmailAutomationActiveSwitch.tsx +49 -0
- package/src/features/email-automation/components/canvas/EmailAutomationCanvas.tsx +474 -0
- package/src/features/email-automation/components/canvas/EmailAutomationCanvasInspector.tsx +89 -0
- package/src/features/email-automation/components/canvas/EmailAutomationCanvasTab.tsx +32 -0
- package/src/features/email-automation/components/canvas/__tests__/buildCanvasGraph.spec.ts +67 -0
- package/src/features/email-automation/components/canvas/buildCanvasGraph.ts +141 -0
- package/src/features/email-automation/components/canvas/edges/DelayEdge.tsx +68 -0
- package/src/features/email-automation/components/canvas/nodes/EmailStepNode.tsx +43 -0
- package/src/features/email-automation/components/canvas/nodes/TriggerNode.tsx +23 -0
- package/src/features/email-automation/components/canvas/shared/CanvasToolbar.tsx +53 -0
- package/src/features/email-automation/components/canvas/shared/EmptyCanvas.tsx +11 -0
- package/src/features/email-automation/components/canvas/shared/__tests__/autoLayout.spec.ts +42 -0
- package/src/features/email-automation/components/canvas/shared/__tests__/longPress.spec.ts +51 -0
- package/src/features/email-automation/components/canvas/shared/autoLayout.ts +55 -0
- package/src/features/email-automation/components/canvas/shared/index.ts +4 -0
- package/src/features/email-automation/components/canvas/shared/longPress.ts +57 -0
- package/src/features/email-automation/components/containers/EmailAutomationContainer.tsx +80 -0
- package/src/features/email-automation/components/containers/EmailAutomationsListContainer.tsx +27 -0
- package/src/features/email-automation/components/forms/EmailAutomationEditor.tsx +153 -0
- package/src/features/email-automation/components/forms/EmailStepEditor.tsx +151 -0
- package/src/features/email-automation/components/index.ts +15 -0
- package/src/features/email-automation/components/lists/EmailAutomationRecipientsList.tsx +90 -0
- package/src/features/email-automation/components/lists/EmailAutomationsList.tsx +76 -0
- package/src/features/email-automation/components/unsubscribe/EmailUnsubscribe.tsx +99 -0
- package/src/features/email-automation/components/unsubscribe/__tests__/EmailUnsubscribe.spec.tsx +55 -0
- package/src/features/email-automation/contexts/EmailAutomationContext.tsx +175 -0
- package/src/features/email-automation/data/EmailAutomation.ts +87 -0
- package/src/features/email-automation/data/EmailAutomationEnrollment.ts +105 -0
- package/src/features/email-automation/data/EmailAutomationEnrollmentInterface.ts +18 -0
- package/src/features/email-automation/data/EmailAutomationEnrollmentService.ts +31 -0
- package/src/features/email-automation/data/EmailAutomationInterface.ts +22 -0
- package/src/features/email-automation/data/EmailAutomationService.ts +106 -0
- package/src/features/email-automation/data/EmailAutomationStep.ts +104 -0
- package/src/features/email-automation/data/EmailAutomationStepInterface.ts +23 -0
- package/src/features/email-automation/data/EmailAutomationStepService.ts +80 -0
- package/src/features/email-automation/data/EmailAutomationTrigger.ts +28 -0
- package/src/features/email-automation/data/EmailAutomationTriggerInterface.ts +6 -0
- package/src/features/email-automation/data/__tests__/email-automation-models.spec.ts +103 -0
- package/src/features/email-automation/data/email-automation-enrollment.fields.ts +8 -0
- package/src/features/email-automation/data/email-automation.fields.ts +9 -0
- package/src/features/email-automation/data/index.ts +13 -0
- package/src/features/email-automation/hooks/index.ts +2 -0
- package/src/features/email-automation/hooks/useEmailAutomationEnrollmentTableStructure.tsx +126 -0
- package/src/features/email-automation/hooks/useEmailAutomationTableStructure.tsx +119 -0
- package/src/features/email-automation/index.ts +5 -0
- package/src/features/signup-source/SignupSourceCapture.tsx +27 -0
- package/src/features/signup-source/__tests__/signup-source.spec.ts +161 -0
- package/src/features/signup-source/index.ts +9 -0
- package/src/features/signup-source/signup-source.ts +135 -0
- package/src/features/waitlist/components/forms/WaitlistForm.tsx +2 -0
- package/src/features/waitlist/components/forms/WaitlistQuestionnaireRenderer.tsx +15 -13
- package/src/features/waitlist/components/lists/WaitlistList.tsx +60 -22
- package/src/features/waitlist/components/sections/WaitlistHeroSection.tsx +6 -5
- package/src/features/waitlist/config/waitlist.config.ts +23 -0
- package/src/features/waitlist/data/Waitlist.ts +51 -3
- package/src/features/waitlist/data/WaitlistInterface.ts +9 -1
- package/src/features/waitlist/data/WaitlistService.ts +3 -2
- package/src/features/waitlist/data/__tests__/Waitlist.spec.ts +82 -0
- package/src/features/waitlist/data/__tests__/WaitlistService.spec.ts +48 -0
- package/src/features/waitlist/data/waitlist.fields.ts +1 -0
- package/src/features/waitlist/hooks/__tests__/formatSignupSource.spec.ts +24 -0
- package/src/features/waitlist/hooks/formatSignupSource.ts +14 -0
- package/src/features/waitlist/hooks/useWaitlistTableStructure.tsx +26 -14
- package/src/hooks/__tests__/usePageTracker.spec.tsx +16 -10
- package/src/index.ts +3 -0
- package/dist/auth.interface-CeFCz9NU.d.mts +0 -34
- package/dist/auth.interface-D1R7VVky.d.ts +0 -34
- package/dist/chunk-5JADF7ZA.mjs.map +0 -1
- package/dist/chunk-OCJEXAGE.mjs.map +0 -1
- package/dist/chunk-OKUGTXDN.mjs.map +0 -1
- package/dist/chunk-SZQNUGDG.js.map +0 -1
- package/dist/chunk-TO6IYGML.js.map +0 -1
- package/dist/chunk-VVJDWQEI.js.map +0 -1
- /package/dist/{BlockNoteEditor-66FJUDL4.mjs.map → BlockNoteEditor-WTSQANKI.mjs.map} +0 -0
- /package/dist/{BlockNoteViewer-SUK7OMWH.mjs.map → BlockNoteViewer-JVKWJZ6J.mjs.map} +0 -0
- /package/dist/{chunk-KL7CAQP3.mjs.map → chunk-FNTQQFLH.mjs.map} +0 -0
- /package/dist/{chunk-XLVRAHUK.mjs.map → chunk-QKZUE5OF.mjs.map} +0 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export const LONG_PRESS_MS = 500;
|
|
2
|
+
export const LONG_PRESS_MOVE_TOLERANCE_PX = 10;
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Touch long-press detector for canvas nodes. A finger held still for
|
|
6
|
+
* LONG_PRESS_MS on a node fires `onLongPress` with the press point; any move
|
|
7
|
+
* beyond the tolerance (a pan or a node drag), a lift or a cancel aborts it.
|
|
8
|
+
* After a fired press, `consumeClick()` returns true once, so the caller can
|
|
9
|
+
* swallow the click that the finger lift produces.
|
|
10
|
+
*/
|
|
11
|
+
export function createLongPressTracker(params: {
|
|
12
|
+
onLongPress: (press: { target: string; clientX: number; clientY: number }) => void;
|
|
13
|
+
delayMs?: number;
|
|
14
|
+
moveTolerancePx?: number;
|
|
15
|
+
setTimer?: (fn: () => void, ms: number) => unknown;
|
|
16
|
+
clearTimer?: (handle: unknown) => void;
|
|
17
|
+
}) {
|
|
18
|
+
const delay = params.delayMs ?? LONG_PRESS_MS;
|
|
19
|
+
const tolerance = params.moveTolerancePx ?? LONG_PRESS_MOVE_TOLERANCE_PX;
|
|
20
|
+
const setTimer = params.setTimer ?? ((fn: () => void, ms: number) => setTimeout(fn, ms));
|
|
21
|
+
const clearTimer = params.clearTimer ?? ((handle: unknown) => clearTimeout(handle as ReturnType<typeof setTimeout>));
|
|
22
|
+
|
|
23
|
+
let timer: unknown = null;
|
|
24
|
+
let start: { x: number; y: number } | null = null;
|
|
25
|
+
let fired = false;
|
|
26
|
+
|
|
27
|
+
const cancel = () => {
|
|
28
|
+
if (timer !== null) clearTimer(timer);
|
|
29
|
+
timer = null;
|
|
30
|
+
start = null;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
down(target: string, clientX: number, clientY: number) {
|
|
35
|
+
cancel();
|
|
36
|
+
fired = false;
|
|
37
|
+
start = { x: clientX, y: clientY };
|
|
38
|
+
timer = setTimer(() => {
|
|
39
|
+
timer = null;
|
|
40
|
+
start = null;
|
|
41
|
+
fired = true;
|
|
42
|
+
params.onLongPress({ target, clientX, clientY });
|
|
43
|
+
}, delay);
|
|
44
|
+
},
|
|
45
|
+
move(clientX: number, clientY: number) {
|
|
46
|
+
if (!start) return;
|
|
47
|
+
if (Math.hypot(clientX - start.x, clientY - start.y) > tolerance) cancel();
|
|
48
|
+
},
|
|
49
|
+
up: cancel,
|
|
50
|
+
cancel,
|
|
51
|
+
consumeClick(): boolean {
|
|
52
|
+
const wasFired = fired;
|
|
53
|
+
fired = false;
|
|
54
|
+
return wasFired;
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useTranslations } from "next-intl";
|
|
4
|
+
import { RoundPageContainer } from "../../../../components";
|
|
5
|
+
import { SharedProvider } from "../../../../contexts/SharedContext";
|
|
6
|
+
import { Modules } from "../../../../core";
|
|
7
|
+
import { usePageUrlGenerator } from "../../../../hooks";
|
|
8
|
+
import { BreadcrumbItemData } from "../../../../interfaces";
|
|
9
|
+
import { EmailAutomationProvider, useEmailAutomation } from "../../contexts/EmailAutomationContext";
|
|
10
|
+
import { EmailAutomationActiveSwitch } from "../canvas/EmailAutomationActiveSwitch";
|
|
11
|
+
import { EmailAutomationCanvasTab } from "../canvas/EmailAutomationCanvasTab";
|
|
12
|
+
import { EmailAutomationEditor } from "../forms/EmailAutomationEditor";
|
|
13
|
+
import { EmailAutomationRecipientsList } from "../lists/EmailAutomationRecipientsList";
|
|
14
|
+
|
|
15
|
+
export type EmailAutomationContainerProps = {
|
|
16
|
+
automationId: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export function EmailAutomationContainer({ automationId }: EmailAutomationContainerProps) {
|
|
20
|
+
return (
|
|
21
|
+
<EmailAutomationProvider automationId={automationId}>
|
|
22
|
+
<EmailAutomationShell />
|
|
23
|
+
</EmailAutomationProvider>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function EmailAutomationShell() {
|
|
28
|
+
const t = useTranslations();
|
|
29
|
+
const generateUrl = usePageUrlGenerator();
|
|
30
|
+
const { automation, reload } = useEmailAutomation();
|
|
31
|
+
|
|
32
|
+
if (!automation) return null;
|
|
33
|
+
|
|
34
|
+
const breadcrumbs: BreadcrumbItemData[] = [
|
|
35
|
+
{ name: t("emailAutomations.admin.title"), href: generateUrl({ page: Modules.EmailAutomation }) },
|
|
36
|
+
{ name: automation.name },
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
// The page chrome (title + header functions) is published here rather than by
|
|
40
|
+
// AdministrationProvider: SharedProvider replaces its value for the subtree,
|
|
41
|
+
// so this one carries the administration title type plus the automation name.
|
|
42
|
+
return (
|
|
43
|
+
<SharedProvider
|
|
44
|
+
value={{
|
|
45
|
+
breadcrumbs,
|
|
46
|
+
title: {
|
|
47
|
+
type: t("emailAutomations.admin.title"),
|
|
48
|
+
element: automation.name,
|
|
49
|
+
functions: [
|
|
50
|
+
<EmailAutomationActiveSwitch key="emailAutomationActiveSwitch" />,
|
|
51
|
+
<EmailAutomationEditor key="emailAutomationEditor" automation={automation} onSuccess={reload} />,
|
|
52
|
+
],
|
|
53
|
+
},
|
|
54
|
+
}}
|
|
55
|
+
>
|
|
56
|
+
<RoundPageContainer
|
|
57
|
+
module={Modules.EmailAutomation}
|
|
58
|
+
id={automation.id}
|
|
59
|
+
fullWidth
|
|
60
|
+
forceHeader
|
|
61
|
+
// The canvas is sized h-full: it needs the viewport-bound shell and a
|
|
62
|
+
// fillHeight tab, or it resolves to zero height.
|
|
63
|
+
scroll="fixed"
|
|
64
|
+
tabs={[
|
|
65
|
+
{
|
|
66
|
+
sectionKey: "canvas",
|
|
67
|
+
label: t("emailAutomations.tabs.canvas"),
|
|
68
|
+
content: <EmailAutomationCanvasTab />,
|
|
69
|
+
fillHeight: true,
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
sectionKey: "recipients",
|
|
73
|
+
label: t("emailAutomations.tabs.recipients"),
|
|
74
|
+
content: <EmailAutomationRecipientsList />,
|
|
75
|
+
},
|
|
76
|
+
]}
|
|
77
|
+
/>
|
|
78
|
+
</SharedProvider>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { RoundPageContainer } from "../../../../components";
|
|
4
|
+
import { Modules } from "../../../../core";
|
|
5
|
+
import { AdministrationProvider } from "../../../administration/contexts/AdministrationContext";
|
|
6
|
+
import { EmailAutomationsList } from "../lists/EmailAutomationsList";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Page container for the administrative email-automation list.
|
|
10
|
+
*
|
|
11
|
+
* Client component by necessity: `module={Modules.EmailAutomation}` is a
|
|
12
|
+
* registry entry carrying an icon component and methods, which a Server
|
|
13
|
+
* Component cannot pass across the boundary. Routes mount this container
|
|
14
|
+
* instead — the same shape as every other list page.
|
|
15
|
+
*
|
|
16
|
+
* No `forceHeader`: ContentListTable draws its own header row with the title
|
|
17
|
+
* and the create action, exactly as the company and user list pages do.
|
|
18
|
+
*/
|
|
19
|
+
export function EmailAutomationsListContainer() {
|
|
20
|
+
return (
|
|
21
|
+
<AdministrationProvider>
|
|
22
|
+
<RoundPageContainer module={Modules.EmailAutomation} fullWidth>
|
|
23
|
+
<EmailAutomationsList fullWidth />
|
|
24
|
+
</RoundPageContainer>
|
|
25
|
+
</AdministrationProvider>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { zodResolver } from "@hookform/resolvers/zod";
|
|
4
|
+
import { useTranslations } from "next-intl";
|
|
5
|
+
import { ReactNode, useCallback, useEffect, useMemo, useState } from "react";
|
|
6
|
+
import { useForm } from "react-hook-form";
|
|
7
|
+
import { v4 } from "uuid";
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
import { EditorSheet, FormInput, FormSelect, FormSwitch } from "../../../../components";
|
|
10
|
+
import { Modules } from "../../../../core";
|
|
11
|
+
import { useI18nRouter } from "../../../../i18n";
|
|
12
|
+
import { EmailAutomationInput, EmailAutomationInterface } from "../../data/EmailAutomationInterface";
|
|
13
|
+
import { EmailAutomationService } from "../../data/EmailAutomationService";
|
|
14
|
+
import { EmailAutomationTriggerInterface } from "../../data/EmailAutomationTriggerInterface";
|
|
15
|
+
|
|
16
|
+
export type EmailAutomationEditorProps = {
|
|
17
|
+
automation?: EmailAutomationInterface;
|
|
18
|
+
propagateChanges?: (automation: EmailAutomationInterface) => void;
|
|
19
|
+
onSuccess?: () => void | Promise<void>;
|
|
20
|
+
trigger?: ReactNode;
|
|
21
|
+
forceShow?: boolean;
|
|
22
|
+
onClose?: () => void;
|
|
23
|
+
dialogOpen?: boolean;
|
|
24
|
+
onDialogOpenChange?: (open: boolean) => void;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
function EmailAutomationEditorInternal({
|
|
28
|
+
automation,
|
|
29
|
+
propagateChanges,
|
|
30
|
+
onSuccess,
|
|
31
|
+
trigger,
|
|
32
|
+
forceShow,
|
|
33
|
+
onClose,
|
|
34
|
+
dialogOpen,
|
|
35
|
+
onDialogOpenChange,
|
|
36
|
+
}: EmailAutomationEditorProps) {
|
|
37
|
+
const t = useTranslations();
|
|
38
|
+
const router = useI18nRouter();
|
|
39
|
+
const isEdit = !!automation;
|
|
40
|
+
const [triggers, setTriggers] = useState<EmailAutomationTriggerInterface[]>([]);
|
|
41
|
+
|
|
42
|
+
// Trigger labels are plain strings from the app config, not i18n keys.
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
let active = true;
|
|
45
|
+
EmailAutomationService.findTriggers()
|
|
46
|
+
.then((list) => {
|
|
47
|
+
if (active) setTriggers(list);
|
|
48
|
+
})
|
|
49
|
+
.catch(() => {});
|
|
50
|
+
return () => {
|
|
51
|
+
active = false;
|
|
52
|
+
};
|
|
53
|
+
}, []);
|
|
54
|
+
|
|
55
|
+
const triggerValues = useMemo(() => triggers.map((entry) => ({ id: entry.key, text: entry.label })), [triggers]);
|
|
56
|
+
|
|
57
|
+
const formSchema = useMemo(
|
|
58
|
+
() =>
|
|
59
|
+
z.object({
|
|
60
|
+
id: z.uuidv4(),
|
|
61
|
+
name: z.string().min(1, { message: t("emailAutomations.fields.name.error") }),
|
|
62
|
+
trigger: z.string().optional(),
|
|
63
|
+
exitTrigger: z.string().optional(),
|
|
64
|
+
replyTo: z.string().refine((value) => value === "" || z.email().safeParse(value).success, {
|
|
65
|
+
message: t("emailAutomations.fields.replyTo.error"),
|
|
66
|
+
}),
|
|
67
|
+
isActive: z.boolean(),
|
|
68
|
+
}),
|
|
69
|
+
[t],
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const getDefaultValues = useCallback(
|
|
73
|
+
() => ({
|
|
74
|
+
id: automation?.id ?? v4(),
|
|
75
|
+
name: automation?.name ?? "",
|
|
76
|
+
trigger: automation?.trigger ?? "",
|
|
77
|
+
exitTrigger: automation?.exitTrigger ?? "",
|
|
78
|
+
replyTo: automation?.replyTo ?? "",
|
|
79
|
+
isActive: automation?.isActive ?? false,
|
|
80
|
+
}),
|
|
81
|
+
[automation],
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
const form = useForm<z.infer<typeof formSchema>>({
|
|
85
|
+
resolver: zodResolver(formSchema),
|
|
86
|
+
defaultValues: getDefaultValues(),
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<EditorSheet
|
|
91
|
+
form={form}
|
|
92
|
+
entityType={t("emailAutomations.admin.title")}
|
|
93
|
+
entityName={automation?.name}
|
|
94
|
+
title={isEdit ? t("emailAutomations.admin.editAutomation") : t("emailAutomations.admin.newAutomation")}
|
|
95
|
+
isEdit={isEdit}
|
|
96
|
+
module={Modules.EmailAutomation}
|
|
97
|
+
size="md"
|
|
98
|
+
propagateChanges={propagateChanges}
|
|
99
|
+
onSuccess={onSuccess}
|
|
100
|
+
onNavigate={(url) => router.push(url)}
|
|
101
|
+
onSubmit={async (values) => {
|
|
102
|
+
const payload: EmailAutomationInput = {
|
|
103
|
+
id: values.id,
|
|
104
|
+
name: values.name,
|
|
105
|
+
trigger: values.trigger || undefined,
|
|
106
|
+
exitTrigger: values.exitTrigger || undefined,
|
|
107
|
+
replyTo: values.replyTo || undefined,
|
|
108
|
+
isActive: values.isActive,
|
|
109
|
+
};
|
|
110
|
+
return isEdit ? await EmailAutomationService.update(payload) : await EmailAutomationService.create(payload);
|
|
111
|
+
}}
|
|
112
|
+
onReset={getDefaultValues}
|
|
113
|
+
trigger={trigger}
|
|
114
|
+
forceShow={forceShow}
|
|
115
|
+
onClose={onClose}
|
|
116
|
+
dialogOpen={dialogOpen}
|
|
117
|
+
onDialogOpenChange={onDialogOpenChange}
|
|
118
|
+
>
|
|
119
|
+
<div className="flex w-full flex-col gap-y-4">
|
|
120
|
+
<FormInput form={form} id="name" name={t("emailAutomations.fields.name.label")} isRequired />
|
|
121
|
+
<FormSelect
|
|
122
|
+
form={form}
|
|
123
|
+
id="trigger"
|
|
124
|
+
name={t("emailAutomations.fields.trigger.label")}
|
|
125
|
+
placeholder={t("emailAutomations.fields.trigger.none")}
|
|
126
|
+
values={triggerValues}
|
|
127
|
+
allowEmpty
|
|
128
|
+
/>
|
|
129
|
+
<FormSelect
|
|
130
|
+
form={form}
|
|
131
|
+
id="exitTrigger"
|
|
132
|
+
name={t("emailAutomations.fields.exitTrigger.label")}
|
|
133
|
+
placeholder={t("emailAutomations.fields.trigger.none")}
|
|
134
|
+
values={triggerValues}
|
|
135
|
+
allowEmpty
|
|
136
|
+
/>
|
|
137
|
+
<FormInput form={form} id="replyTo" name={t("emailAutomations.fields.replyTo.label")} />
|
|
138
|
+
<FormSwitch
|
|
139
|
+
form={form}
|
|
140
|
+
id="isActive"
|
|
141
|
+
name={t("emailAutomations.fields.isActive.label")}
|
|
142
|
+
description={t("emailAutomations.fields.isActive.help")}
|
|
143
|
+
/>
|
|
144
|
+
</div>
|
|
145
|
+
</EditorSheet>
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function EmailAutomationEditor(props: EmailAutomationEditorProps) {
|
|
150
|
+
return <EmailAutomationEditorInternal {...props} />;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export default EmailAutomationEditor;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { zodResolver } from "@hookform/resolvers/zod";
|
|
4
|
+
import { useTranslations } from "next-intl";
|
|
5
|
+
import { ReactNode, useCallback, useMemo } from "react";
|
|
6
|
+
import { useForm } from "react-hook-form";
|
|
7
|
+
import { v4 } from "uuid";
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
import { EditorSheet, FormInput, FormTextarea } from "../../../../components";
|
|
10
|
+
import { Modules } from "../../../../core";
|
|
11
|
+
import { useEmailAutomation } from "../../contexts/EmailAutomationContext";
|
|
12
|
+
import { EmailAutomationStepInput, EmailAutomationStepInterface } from "../../data/EmailAutomationStepInterface";
|
|
13
|
+
import { EmailAutomationStepService } from "../../data/EmailAutomationStepService";
|
|
14
|
+
|
|
15
|
+
/** The fixed variable set the renderer fills (spec § "Rendering"). */
|
|
16
|
+
const TEMPLATE_VARIABLES = ["{{name}}", "{{email}}", "{{url}}", "{{unsubscribeUrl}}", "{{unsubscribeAllUrl}}"];
|
|
17
|
+
|
|
18
|
+
export type EmailStepEditorProps = {
|
|
19
|
+
step?: EmailAutomationStepInterface;
|
|
20
|
+
automationId: string;
|
|
21
|
+
/** Canvas position for a step created from the editor. */
|
|
22
|
+
initialPosition?: { x: number; y: number };
|
|
23
|
+
/** The head step's delay counts from entering the automation, not from a previous email. */
|
|
24
|
+
isHead?: boolean;
|
|
25
|
+
trigger?: ReactNode;
|
|
26
|
+
forceShow?: boolean;
|
|
27
|
+
onClose?: () => void;
|
|
28
|
+
dialogOpen?: boolean;
|
|
29
|
+
onDialogOpenChange?: (open: boolean) => void;
|
|
30
|
+
onSuccess?: () => void | Promise<void>;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
function EmailStepEditorInternal({
|
|
34
|
+
step,
|
|
35
|
+
automationId,
|
|
36
|
+
initialPosition,
|
|
37
|
+
isHead,
|
|
38
|
+
trigger,
|
|
39
|
+
forceShow,
|
|
40
|
+
onClose,
|
|
41
|
+
dialogOpen,
|
|
42
|
+
onDialogOpenChange,
|
|
43
|
+
onSuccess,
|
|
44
|
+
}: EmailStepEditorProps) {
|
|
45
|
+
const t = useTranslations();
|
|
46
|
+
const { reload } = useEmailAutomation();
|
|
47
|
+
const isEdit = !!step;
|
|
48
|
+
|
|
49
|
+
const formSchema = useMemo(
|
|
50
|
+
() =>
|
|
51
|
+
z.object({
|
|
52
|
+
id: z.uuidv4(),
|
|
53
|
+
subject: z.string().min(1, { message: t("emailAutomations.step.fields.subject.error") }),
|
|
54
|
+
delayHours: z.number().int().min(0),
|
|
55
|
+
body: z.string().min(1, { message: t("emailAutomations.step.fields.body.error") }),
|
|
56
|
+
}),
|
|
57
|
+
[t],
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
const getDefaultValues = useCallback(
|
|
61
|
+
() => ({
|
|
62
|
+
id: step?.id ?? v4(),
|
|
63
|
+
subject: step?.subject ?? "",
|
|
64
|
+
delayHours: step?.delayHours ?? 24,
|
|
65
|
+
body: step?.body ?? "",
|
|
66
|
+
}),
|
|
67
|
+
[step],
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
const form = useForm<z.infer<typeof formSchema>>({
|
|
71
|
+
resolver: zodResolver(formSchema),
|
|
72
|
+
defaultValues: getDefaultValues(),
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<EditorSheet
|
|
77
|
+
form={form}
|
|
78
|
+
entityType={t("emailAutomations.step.title")}
|
|
79
|
+
entityName={step?.subject}
|
|
80
|
+
title={isEdit ? t("emailAutomations.step.editTitle") : t("emailAutomations.step.title")}
|
|
81
|
+
isEdit={isEdit}
|
|
82
|
+
module={Modules.EmailAutomationStep}
|
|
83
|
+
size="lg"
|
|
84
|
+
onSubmit={async (values) => {
|
|
85
|
+
const payload: EmailAutomationStepInput = {
|
|
86
|
+
id: values.id,
|
|
87
|
+
automationId,
|
|
88
|
+
subject: values.subject,
|
|
89
|
+
body: values.body,
|
|
90
|
+
delayHours: values.delayHours,
|
|
91
|
+
positionX: step ? step.positionX : initialPosition?.x,
|
|
92
|
+
positionY: step ? step.positionY : initialPosition?.y,
|
|
93
|
+
};
|
|
94
|
+
return isEdit
|
|
95
|
+
? await EmailAutomationStepService.update(payload)
|
|
96
|
+
: await EmailAutomationStepService.create(payload);
|
|
97
|
+
}}
|
|
98
|
+
// Save → reload the canvas, then whatever the caller adds.
|
|
99
|
+
onSuccess={async () => {
|
|
100
|
+
await reload();
|
|
101
|
+
await onSuccess?.();
|
|
102
|
+
}}
|
|
103
|
+
onReset={getDefaultValues}
|
|
104
|
+
trigger={trigger}
|
|
105
|
+
forceShow={forceShow}
|
|
106
|
+
onClose={onClose}
|
|
107
|
+
dialogOpen={dialogOpen}
|
|
108
|
+
onDialogOpenChange={onDialogOpenChange}
|
|
109
|
+
>
|
|
110
|
+
<div className="flex w-full flex-col gap-y-4">
|
|
111
|
+
<FormInput form={form} id="subject" name={t("emailAutomations.step.fields.subject.label")} isRequired />
|
|
112
|
+
<FormInput
|
|
113
|
+
form={form}
|
|
114
|
+
id="delayHours"
|
|
115
|
+
type="number"
|
|
116
|
+
name={t("emailAutomations.step.fields.delayHours.label")}
|
|
117
|
+
description={
|
|
118
|
+
isHead ? t("emailAutomations.canvas.hoursAfterEntering") : t("emailAutomations.canvas.hoursAfterPrevious")
|
|
119
|
+
}
|
|
120
|
+
/>
|
|
121
|
+
{/* FormTextarea has no `rows` prop; min-h-72 is the height of ~14 rows. */}
|
|
122
|
+
<FormTextarea
|
|
123
|
+
form={form}
|
|
124
|
+
id="body"
|
|
125
|
+
name={t("emailAutomations.step.fields.body.label")}
|
|
126
|
+
className="min-h-72"
|
|
127
|
+
isRequired
|
|
128
|
+
description={
|
|
129
|
+
<>
|
|
130
|
+
<span className="block">
|
|
131
|
+
{t("emailAutomations.step.variablesHint")}{" "}
|
|
132
|
+
{TEMPLATE_VARIABLES.map((variable) => (
|
|
133
|
+
<span key={variable} className="me-2 text-foreground">
|
|
134
|
+
{variable}
|
|
135
|
+
</span>
|
|
136
|
+
))}
|
|
137
|
+
</span>
|
|
138
|
+
<span className="mt-1 block">{t("emailAutomations.step.unsubscribeHint")}</span>
|
|
139
|
+
</>
|
|
140
|
+
}
|
|
141
|
+
/>
|
|
142
|
+
</div>
|
|
143
|
+
</EditorSheet>
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function EmailStepEditor(props: EmailStepEditorProps) {
|
|
148
|
+
return <EmailStepEditorInternal {...props} />;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export default EmailStepEditor;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export * from "./containers/EmailAutomationsListContainer";
|
|
2
|
+
export * from "./containers/EmailAutomationContainer";
|
|
3
|
+
export * from "./lists/EmailAutomationsList";
|
|
4
|
+
export * from "./lists/EmailAutomationRecipientsList";
|
|
5
|
+
export * from "./forms/EmailAutomationEditor";
|
|
6
|
+
export * from "./forms/EmailStepEditor";
|
|
7
|
+
export * from "./canvas/EmailAutomationActiveSwitch";
|
|
8
|
+
export * from "./canvas/EmailAutomationCanvasTab";
|
|
9
|
+
export * from "./canvas/EmailAutomationCanvas";
|
|
10
|
+
export * from "./canvas/EmailAutomationCanvasInspector";
|
|
11
|
+
export * from "./canvas/nodes/TriggerNode";
|
|
12
|
+
export * from "./canvas/nodes/EmailStepNode";
|
|
13
|
+
export * from "./canvas/edges/DelayEdge";
|
|
14
|
+
export * from "./canvas/buildCanvasGraph";
|
|
15
|
+
export * from "./unsubscribe/EmailUnsubscribe";
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { RefreshCw } from "lucide-react";
|
|
4
|
+
import { useTranslations } from "next-intl";
|
|
5
|
+
import { useMemo, useState } from "react";
|
|
6
|
+
import { ContentListTable } from "../../../../components";
|
|
7
|
+
import { Modules } from "../../../../core";
|
|
8
|
+
import { DataListRetriever, useDataListRetriever } from "../../../../hooks";
|
|
9
|
+
import { Button, Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../../../../shadcnui";
|
|
10
|
+
import { useEmailAutomation } from "../../contexts/EmailAutomationContext";
|
|
11
|
+
import { EmailAutomationEnrollmentFields } from "../../data/email-automation-enrollment.fields";
|
|
12
|
+
import { EmailAutomationEnrollmentInterface } from "../../data/EmailAutomationEnrollmentInterface";
|
|
13
|
+
import { EmailAutomationService } from "../../data/EmailAutomationService";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Read-only list of the people in this automation (owner decision 6: no row
|
|
17
|
+
* actions). Rendered inside a tab, so it keeps its own bordered card (no
|
|
18
|
+
* `fullWidth`), as PricesList does inside ProductContainer.
|
|
19
|
+
*/
|
|
20
|
+
export function EmailAutomationRecipientsList() {
|
|
21
|
+
const t = useTranslations();
|
|
22
|
+
const { automation } = useEmailAutomation();
|
|
23
|
+
const [statusFilter, setStatusFilter] = useState<string>("all");
|
|
24
|
+
|
|
25
|
+
const data: DataListRetriever<EmailAutomationEnrollmentInterface> = useDataListRetriever({
|
|
26
|
+
retriever: (params) => EmailAutomationService.findEnrollments(params),
|
|
27
|
+
retrieverParams: { automationId: automation?.id },
|
|
28
|
+
module: Modules.EmailAutomationEnrollment,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const handleStatusChange = (value: string | null) => {
|
|
32
|
+
const status = value ?? "all";
|
|
33
|
+
setStatusFilter(status);
|
|
34
|
+
data.addAdditionalParameter("status", status === "all" ? null : status);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const statusItems = useMemo(
|
|
38
|
+
() => ({
|
|
39
|
+
all: t("emailAutomations.recipients.status.all"),
|
|
40
|
+
active: t("emailAutomations.recipients.status.active"),
|
|
41
|
+
completed: t("emailAutomations.recipients.status.completed"),
|
|
42
|
+
unsubscribed: t("emailAutomations.recipients.status.unsubscribed"),
|
|
43
|
+
exited: t("emailAutomations.recipients.status.exited"),
|
|
44
|
+
failed: t("emailAutomations.recipients.status.failed"),
|
|
45
|
+
}),
|
|
46
|
+
[t],
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const filters = (
|
|
50
|
+
<div className="flex items-center gap-4">
|
|
51
|
+
<Select items={statusItems} value={statusFilter} onValueChange={handleStatusChange}>
|
|
52
|
+
<SelectTrigger className="w-40">
|
|
53
|
+
<SelectValue />
|
|
54
|
+
</SelectTrigger>
|
|
55
|
+
<SelectContent>
|
|
56
|
+
<SelectItem value="all">{t("emailAutomations.recipients.status.all")}</SelectItem>
|
|
57
|
+
<SelectItem value="active">{t("emailAutomations.recipients.status.active")}</SelectItem>
|
|
58
|
+
<SelectItem value="completed">{t("emailAutomations.recipients.status.completed")}</SelectItem>
|
|
59
|
+
<SelectItem value="unsubscribed">{t("emailAutomations.recipients.status.unsubscribed")}</SelectItem>
|
|
60
|
+
<SelectItem value="exited">{t("emailAutomations.recipients.status.exited")}</SelectItem>
|
|
61
|
+
<SelectItem value="failed">{t("emailAutomations.recipients.status.failed")}</SelectItem>
|
|
62
|
+
</SelectContent>
|
|
63
|
+
</Select>
|
|
64
|
+
|
|
65
|
+
<Button variant="outline" size="icon" onClick={() => void data.refresh()} disabled={!data.isLoaded}>
|
|
66
|
+
<RefreshCw className={`h-4 w-4 ${!data.isLoaded ? "animate-spin" : ""}`} />
|
|
67
|
+
</Button>
|
|
68
|
+
</div>
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<ContentListTable
|
|
73
|
+
data={data}
|
|
74
|
+
fields={[
|
|
75
|
+
EmailAutomationEnrollmentFields.email,
|
|
76
|
+
EmailAutomationEnrollmentFields.name,
|
|
77
|
+
EmailAutomationEnrollmentFields.status,
|
|
78
|
+
EmailAutomationEnrollmentFields.currentStep,
|
|
79
|
+
EmailAutomationEnrollmentFields.nextSendAt,
|
|
80
|
+
EmailAutomationEnrollmentFields.sentCount,
|
|
81
|
+
]}
|
|
82
|
+
tableGeneratorType={Modules.EmailAutomationEnrollment}
|
|
83
|
+
filters={filters}
|
|
84
|
+
/* The enrolments endpoint takes no search term, so rendering the box would
|
|
85
|
+
give the page a control that silently does nothing. */
|
|
86
|
+
allowSearch={false}
|
|
87
|
+
emptyState={t("emailAutomations.recipients.empty")}
|
|
88
|
+
/>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { MailIcon } from "lucide-react";
|
|
4
|
+
import { useTranslations } from "next-intl";
|
|
5
|
+
import { ReactNode, useEffect, useMemo, useState } from "react";
|
|
6
|
+
import { ContentListTable } from "../../../../components";
|
|
7
|
+
import { Modules } from "../../../../core";
|
|
8
|
+
import { DataListRetriever, useDataListRetriever } from "../../../../hooks";
|
|
9
|
+
import { EmptyState } from "../../../../shadcnui";
|
|
10
|
+
import { EmailAutomationFields } from "../../data/email-automation.fields";
|
|
11
|
+
import { EmailAutomationInterface } from "../../data/EmailAutomationInterface";
|
|
12
|
+
import { EmailAutomationService } from "../../data/EmailAutomationService";
|
|
13
|
+
import { EmailAutomationTriggerInterface } from "../../data/EmailAutomationTriggerInterface";
|
|
14
|
+
import EmailAutomationEditor from "../forms/EmailAutomationEditor";
|
|
15
|
+
|
|
16
|
+
type EmailAutomationsListProps = {
|
|
17
|
+
/**
|
|
18
|
+
* Pass this whenever the list is the page's own content inside a
|
|
19
|
+
* `RoundPageContainer fullWidth`. Without it ContentListTable wraps itself in
|
|
20
|
+
* `rounded-md border`, drawing a second bordered card inside the page's
|
|
21
|
+
* rounded shell.
|
|
22
|
+
*/
|
|
23
|
+
fullWidth?: boolean;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export function EmailAutomationsList({ fullWidth }: EmailAutomationsListProps = {}) {
|
|
27
|
+
const t = useTranslations();
|
|
28
|
+
const [triggers, setTriggers] = useState<EmailAutomationTriggerInterface[]>([]);
|
|
29
|
+
|
|
30
|
+
const data: DataListRetriever<EmailAutomationInterface> = useDataListRetriever({
|
|
31
|
+
retriever: (params) => EmailAutomationService.findMany(params),
|
|
32
|
+
retrieverParams: {},
|
|
33
|
+
module: Modules.EmailAutomation,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// The trigger column shows the label the app declares, not the stored key.
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
let active = true;
|
|
39
|
+
EmailAutomationService.findTriggers()
|
|
40
|
+
.then((list) => {
|
|
41
|
+
if (active) setTriggers(list);
|
|
42
|
+
})
|
|
43
|
+
.catch(() => {});
|
|
44
|
+
return () => {
|
|
45
|
+
active = false;
|
|
46
|
+
};
|
|
47
|
+
}, []);
|
|
48
|
+
|
|
49
|
+
const triggerLabels = useMemo(
|
|
50
|
+
() => Object.fromEntries(triggers.map((trigger) => [trigger.key, trigger.label])),
|
|
51
|
+
[triggers],
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
const functions: ReactNode[] = [<EmailAutomationEditor key="create-email-automation" />];
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<ContentListTable
|
|
58
|
+
data={data}
|
|
59
|
+
fields={[
|
|
60
|
+
EmailAutomationFields.name,
|
|
61
|
+
EmailAutomationFields.trigger,
|
|
62
|
+
EmailAutomationFields.status,
|
|
63
|
+
EmailAutomationFields.steps,
|
|
64
|
+
EmailAutomationFields.active,
|
|
65
|
+
EmailAutomationFields.completed,
|
|
66
|
+
EmailAutomationFields.unsubscribed,
|
|
67
|
+
]}
|
|
68
|
+
tableGeneratorType={Modules.EmailAutomation}
|
|
69
|
+
functions={functions}
|
|
70
|
+
fullWidth={fullWidth}
|
|
71
|
+
title={t("emailAutomations.admin.title")}
|
|
72
|
+
context={{ triggerLabels }}
|
|
73
|
+
emptyState={<EmptyState icon={MailIcon} title={t("emailAutomations.admin.empty")} />}
|
|
74
|
+
/>
|
|
75
|
+
);
|
|
76
|
+
}
|