@carlonicora/nextjs-jsonapi 5.2.0 → 5.4.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-EGY4ZAAM.js → BlockNoteEditor-4YCAIEPT.js} +23 -22
- package/dist/BlockNoteEditor-4YCAIEPT.js.map +1 -0
- package/dist/{BlockNoteEditor-2QJVK2HH.mjs → BlockNoteEditor-YQL2SGFR.mjs} +8 -7
- package/dist/BlockNoteEditor-YQL2SGFR.mjs.map +1 -0
- package/dist/{BlockNoteViewer-6BQXJ4TB.mjs → BlockNoteViewer-AEJKJ27Q.mjs} +2 -2
- package/dist/{BlockNoteViewer-I2MMFH3S.js → BlockNoteViewer-YL3J7J2Y.js} +3 -3
- package/dist/{BlockNoteViewer-I2MMFH3S.js.map → BlockNoteViewer-YL3J7J2Y.js.map} +1 -1
- package/dist/BlogCategoryFields-DO48vOTE.d.mts +14 -0
- package/dist/BlogCategoryFields-DO48vOTE.d.ts +14 -0
- package/dist/BlogPostInterface-BB_4wcDR.d.mts +124 -0
- package/dist/BlogPostInterface-bWAT1rfR.d.ts +124 -0
- package/dist/billing/index.js +441 -441
- package/dist/billing/index.mjs +3 -3
- package/dist/blog-public.types-BbrzsC1c.d.mts +30 -0
- package/dist/blog-public.types-BbrzsC1c.d.ts +30 -0
- package/dist/{chunk-3VEQ5JZJ.js → chunk-3NLAXVIN.js} +860 -849
- package/dist/{chunk-3VEQ5JZJ.js.map → chunk-3NLAXVIN.js.map} +1 -1
- package/dist/{chunk-YMUPTXOT.mjs → chunk-3YW35YHQ.mjs} +1 -101
- package/dist/chunk-3YW35YHQ.mjs.map +1 -0
- package/dist/{chunk-EXICQC4A.js → chunk-5PPG6PL7.js} +3 -3
- package/dist/{chunk-EXICQC4A.js.map → chunk-5PPG6PL7.js.map} +1 -1
- package/dist/{chunk-NPKFMRH5.mjs → chunk-6DC2WVVB.mjs} +2 -2
- package/dist/{chunk-2Z3ZCYXX.mjs → chunk-EUKTBOI2.mjs} +19 -8
- package/dist/chunk-EUKTBOI2.mjs.map +1 -0
- package/dist/{chunk-THKYH65N.js → chunk-KSYQEKJX.js} +11 -111
- package/dist/chunk-KSYQEKJX.js.map +1 -0
- package/dist/{chunk-S5MQDWWC.js → chunk-NFBOSPNJ.js} +1451 -176
- package/dist/chunk-NFBOSPNJ.js.map +1 -0
- package/dist/{chunk-JEZQTZQ4.mjs → chunk-QMYQRPQC.mjs} +2 -2
- package/dist/{chunk-HNOWSFM5.mjs → chunk-WC63PJSC.mjs} +1278 -3
- package/dist/chunk-WC63PJSC.mjs.map +1 -0
- package/dist/chunk-XCHSX3VC.mjs +107 -0
- package/dist/chunk-XCHSX3VC.mjs.map +1 -0
- package/dist/{chunk-75MLT74B.js → chunk-YNCQSWQW.js} +8 -8
- package/dist/{chunk-75MLT74B.js.map → chunk-YNCQSWQW.js.map} +1 -1
- package/dist/chunk-YW4ZCEWB.js +107 -0
- package/dist/chunk-YW4ZCEWB.js.map +1 -0
- 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 +15 -4
- package/dist/components/index.d.ts +15 -4
- package/dist/components/index.js +4 -4
- package/dist/components/index.mjs +3 -3
- package/dist/contexts/index.js +4 -4
- package/dist/contexts/index.mjs +3 -3
- package/dist/core/index.d.mts +400 -3
- package/dist/core/index.d.ts +400 -3
- package/dist/core/index.js +64 -2
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +63 -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 +6 -5
- package/dist/features/analytics/server.js.map +1 -1
- package/dist/features/analytics/server.mjs +4 -3
- package/dist/features/analytics/server.mjs.map +1 -1
- package/dist/features/blog/index.css +30 -0
- package/dist/features/blog/index.css.map +1 -0
- package/dist/features/blog/index.d.mts +266 -0
- package/dist/features/blog/index.d.ts +266 -0
- package/dist/features/blog/index.js +1799 -0
- package/dist/features/blog/index.js.map +1 -0
- package/dist/features/blog/index.mjs +1799 -0
- package/dist/features/blog/index.mjs.map +1 -0
- package/dist/features/blog/server.d.mts +33 -0
- package/dist/features/blog/server.d.ts +33 -0
- package/dist/features/blog/server.js +139 -0
- package/dist/features/blog/server.js.map +1 -0
- package/dist/features/blog/server.mjs +139 -0
- package/dist/features/blog/server.mjs.map +1 -0
- 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 -2
- package/dist/index.d.ts +5 -2
- package/dist/index.js +65 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +64 -2
- package/dist/{s3.service-CY-jgOAM.d.ts → s3.service-Bz_K-tjz.d.ts} +11 -0
- package/dist/{s3.service-Sri0QP7Z.d.mts → s3.service-D5zNCEW3.d.mts} +11 -0
- package/dist/server/index.d.mts +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +8 -6
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +12 -10
- package/dist/types-70acd-8C.d.mts +39 -0
- package/dist/types-l8SFleXi.d.ts +39 -0
- package/messages/en.json +210 -0
- package/messages/it.json +210 -0
- package/package.json +21 -1
- package/src/components/editors/BlockNoteEditor.tsx +13 -3
- package/src/components/forms/FormBlockNote.tsx +4 -0
- package/src/components/forms/FormFieldWrapper.tsx +1 -1
- package/src/components/forms/FormTextarea.tsx +2 -1
- package/src/components/navigations/ContentTitle.tsx +14 -3
- package/src/core/index.ts +12 -0
- package/src/core/registry/ModuleRegistry.ts +11 -0
- package/src/email-automations/index.ts +5 -0
- package/src/features/blog/BlogModules.ts +49 -0
- package/src/features/blog/components/admin/BlogAdminContainer.tsx +32 -0
- package/src/features/blog/components/admin/BlogCategoriesList.tsx +56 -0
- package/src/features/blog/components/admin/BlogCategoryEditor.tsx +230 -0
- package/src/features/blog/components/admin/BlogCoverImageField.tsx +123 -0
- package/src/features/blog/components/admin/BlogLayoutSetting.tsx +86 -0
- package/src/features/blog/components/admin/BlogPostComparison.tsx +54 -0
- package/src/features/blog/components/admin/BlogPostContainer.tsx +102 -0
- package/src/features/blog/components/admin/BlogPostDeleter.tsx +35 -0
- package/src/features/blog/components/admin/BlogPostEditor.tsx +207 -0
- package/src/features/blog/components/admin/BlogPostPublishButton.tsx +50 -0
- package/src/features/blog/components/admin/BlogPostTranslationEditor.tsx +110 -0
- package/src/features/blog/components/admin/BlogPostTranslationPanel.tsx +136 -0
- package/src/features/blog/components/admin/BlogPostsList.tsx +58 -0
- package/src/features/blog/components/admin/BlogTranslationStatus.tsx +104 -0
- package/src/features/blog/components/admin/__tests__/BlogPostTranslationPanel.spec.tsx +100 -0
- package/src/features/blog/components/admin/__tests__/BlogPostsList.spec.tsx +30 -0
- package/src/features/blog/components/admin/index.ts +14 -0
- package/src/features/blog/components/public/BlogCategoryChips.tsx +59 -0
- package/src/features/blog/components/public/BlogList.tsx +21 -0
- package/src/features/blog/components/public/BlogListColumn.tsx +53 -0
- package/src/features/blog/components/public/BlogListFeatured.tsx +57 -0
- package/src/features/blog/components/public/BlogListParts.tsx +74 -0
- package/src/features/blog/components/public/BlogListRail.tsx +62 -0
- package/src/features/blog/components/public/BlogPostCard.tsx +132 -0
- package/src/features/blog/components/public/BlogPostMeta.tsx +49 -0
- package/src/features/blog/components/public/BlogPostView.tsx +74 -0
- package/src/features/blog/components/public/BlogSearchBox.tsx +52 -0
- package/src/features/blog/components/public/__tests__/BlogList.spec.tsx +173 -0
- package/src/features/blog/components/public/__tests__/BlogPostView.spec.tsx +128 -0
- package/src/features/blog/components/public/blogPublicHelpers.ts +30 -0
- package/src/features/blog/components/public/index.ts +3 -0
- package/src/features/blog/contexts/BlogPostContext.tsx +66 -0
- package/src/features/blog/contexts/index.ts +1 -0
- package/src/features/blog/data/BlogCategory.ts +93 -0
- package/src/features/blog/data/BlogCategoryFields.ts +4 -0
- package/src/features/blog/data/BlogCategoryInterface.ts +24 -0
- package/src/features/blog/data/BlogCategoryService.ts +54 -0
- package/src/features/blog/data/BlogCategoryTranslation.ts +90 -0
- package/src/features/blog/data/BlogCategoryTranslationInterface.ts +22 -0
- package/src/features/blog/data/BlogCategoryTranslationService.ts +30 -0
- package/src/features/blog/data/BlogPost.spec.ts +123 -0
- package/src/features/blog/data/BlogPost.ts +159 -0
- package/src/features/blog/data/BlogPostFields.ts +7 -0
- package/src/features/blog/data/BlogPostInterface.ts +39 -0
- package/src/features/blog/data/BlogPostService.ts +82 -0
- package/src/features/blog/data/BlogPostTranslation.spec.ts +73 -0
- package/src/features/blog/data/BlogPostTranslation.ts +110 -0
- package/src/features/blog/data/BlogPostTranslationInterface.ts +27 -0
- package/src/features/blog/data/BlogPostTranslationService.ts +30 -0
- package/src/features/blog/data/BlogSettings.ts +37 -0
- package/src/features/blog/data/BlogSettingsInterface.ts +11 -0
- package/src/features/blog/data/BlogSettingsService.ts +24 -0
- package/src/features/blog/data/index.ts +17 -0
- package/src/features/blog/hooks/index.ts +3 -0
- package/src/features/blog/hooks/useBlogCategoryNames.ts +34 -0
- package/src/features/blog/hooks/useBlogCategoryTableStructure.tsx +84 -0
- package/src/features/blog/hooks/useBlogPostTableStructure.tsx +126 -0
- package/src/features/blog/index.ts +15 -0
- package/src/features/blog/server/fetchers.ts +120 -0
- package/src/features/blog/server/toPublic.spec.ts +91 -0
- package/src/features/blog/server/toPublic.ts +36 -0
- package/src/features/blog/server-entry.ts +17 -0
- package/src/features/blog/types/blog-public.types.ts +34 -0
- package/src/features/blog/types/blog.constants.ts +40 -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/s3/data/s3.service.ts +17 -0
- package/dist/BlockNoteEditor-2QJVK2HH.mjs.map +0 -1
- package/dist/BlockNoteEditor-EGY4ZAAM.js.map +0 -1
- package/dist/chunk-2Z3ZCYXX.mjs.map +0 -1
- package/dist/chunk-HNOWSFM5.mjs.map +0 -1
- package/dist/chunk-S5MQDWWC.js.map +0 -1
- package/dist/chunk-THKYH65N.js.map +0 -1
- package/dist/chunk-YMUPTXOT.mjs.map +0 -1
- /package/dist/{BlockNoteViewer-6BQXJ4TB.mjs.map → BlockNoteViewer-AEJKJ27Q.mjs.map} +0 -0
- /package/dist/{chunk-NPKFMRH5.mjs.map → chunk-6DC2WVVB.mjs.map} +0 -0
- /package/dist/{chunk-JEZQTZQ4.mjs.map → chunk-QMYQRPQC.mjs.map} +0 -0
|
@@ -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
|
+
}
|