@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.
- package/dist/{BlockNoteEditor-EGY4ZAAM.js → BlockNoteEditor-UJ6363LC.js} +19 -19
- package/dist/{BlockNoteEditor-EGY4ZAAM.js.map → BlockNoteEditor-UJ6363LC.js.map} +1 -1
- package/dist/{BlockNoteEditor-2QJVK2HH.mjs → BlockNoteEditor-WTSQANKI.mjs} +4 -4
- package/dist/{BlockNoteViewer-I2MMFH3S.js → BlockNoteViewer-EL4JFB6P.js} +3 -3
- package/dist/{BlockNoteViewer-I2MMFH3S.js.map → BlockNoteViewer-EL4JFB6P.js.map} +1 -1
- package/dist/{BlockNoteViewer-6BQXJ4TB.mjs → BlockNoteViewer-JVKWJZ6J.mjs} +2 -2
- package/dist/billing/index.js +441 -441
- package/dist/billing/index.mjs +3 -3
- package/dist/{chunk-3VEQ5JZJ.js → chunk-3BAM23VA.js} +847 -847
- package/dist/chunk-3BAM23VA.js.map +1 -0
- package/dist/{chunk-JEZQTZQ4.mjs → chunk-6WU74VFU.mjs} +2 -2
- package/dist/{chunk-S5MQDWWC.js → chunk-CRI4T47Y.js} +736 -176
- package/dist/chunk-CRI4T47Y.js.map +1 -0
- package/dist/{chunk-75MLT74B.js → chunk-EO75CV76.js} +8 -8
- package/dist/{chunk-75MLT74B.js.map → chunk-EO75CV76.js.map} +1 -1
- package/dist/{chunk-NPKFMRH5.mjs → chunk-FNTQQFLH.mjs} +2 -2
- package/dist/{chunk-HNOWSFM5.mjs → chunk-JQWI62NV.mjs} +563 -3
- package/dist/chunk-JQWI62NV.mjs.map +1 -0
- package/dist/{chunk-2Z3ZCYXX.mjs → chunk-MIZTWBPX.mjs} +5 -5
- package/dist/chunk-MIZTWBPX.mjs.map +1 -0
- package/dist/{chunk-YMUPTXOT.mjs → chunk-QKZUE5OF.mjs} +2 -2
- package/dist/{chunk-EXICQC4A.js → chunk-QQQE3N2X.js} +3 -3
- package/dist/{chunk-EXICQC4A.js.map → chunk-QQQE3N2X.js.map} +1 -1
- package/dist/{chunk-THKYH65N.js → chunk-XGTQWBDJ.js} +4 -4
- package/dist/{chunk-THKYH65N.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 +2 -2
- package/dist/components/index.d.ts +2 -2
- 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 +187 -1
- package/dist/core/index.d.ts +187 -1
- 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 +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +31 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +30 -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/messages/en.json +117 -0
- package/messages/it.json +117 -0
- package/package.json +11 -1
- package/src/components/forms/FormFieldWrapper.tsx +1 -1
- package/src/components/forms/FormTextarea.tsx +2 -1
- 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/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/dist/chunk-2Z3ZCYXX.mjs.map +0 -1
- package/dist/chunk-3VEQ5JZJ.js.map +0 -1
- package/dist/chunk-HNOWSFM5.mjs.map +0 -1
- package/dist/chunk-S5MQDWWC.js.map +0 -1
- /package/dist/{BlockNoteEditor-2QJVK2HH.mjs.map → BlockNoteEditor-WTSQANKI.mjs.map} +0 -0
- /package/dist/{BlockNoteViewer-6BQXJ4TB.mjs.map → BlockNoteViewer-JVKWJZ6J.mjs.map} +0 -0
- /package/dist/{chunk-JEZQTZQ4.mjs.map → chunk-6WU74VFU.mjs.map} +0 -0
- /package/dist/{chunk-NPKFMRH5.mjs.map → chunk-FNTQQFLH.mjs.map} +0 -0
- /package/dist/{chunk-YMUPTXOT.mjs.map → chunk-QKZUE5OF.mjs.map} +0 -0
package/messages/it.json
CHANGED
|
@@ -736,6 +736,15 @@
|
|
|
736
736
|
}
|
|
737
737
|
}
|
|
738
738
|
},
|
|
739
|
+
"canvas": {
|
|
740
|
+
"toolbar": {
|
|
741
|
+
"fitView": "Adatta alla vista",
|
|
742
|
+
"tidy": "Riordina",
|
|
743
|
+
"tidyTooltip": "Cancella le posizioni manuali e rifai il layout automatico",
|
|
744
|
+
"zoomIn": "Ingrandisci",
|
|
745
|
+
"zoomOut": "Riduci"
|
|
746
|
+
}
|
|
747
|
+
},
|
|
739
748
|
"common": {
|
|
740
749
|
"actions": {
|
|
741
750
|
"submitting": "Invio in corso..."
|
|
@@ -906,6 +915,114 @@
|
|
|
906
915
|
},
|
|
907
916
|
"news": "Contenuti recenti pertinenti"
|
|
908
917
|
},
|
|
918
|
+
"emailAutomations": {
|
|
919
|
+
"admin": {
|
|
920
|
+
"editAutomation": "Modifica automazione",
|
|
921
|
+
"empty": "Nessuna automazione email.",
|
|
922
|
+
"newAutomation": "Nuova automazione",
|
|
923
|
+
"title": "Automazioni email"
|
|
924
|
+
},
|
|
925
|
+
"canvas": {
|
|
926
|
+
"addEmail": "Aggiungi email",
|
|
927
|
+
"chainError": "Questo collegamento romperebbe la sequenza.",
|
|
928
|
+
"deleteEmail": "Elimina email",
|
|
929
|
+
"deleteEmailConfirm": "Eliminare questa email? La sequenza si richiude e chi la stava aspettando passa all'email successiva.",
|
|
930
|
+
"emptyBody": "Fai clic destro sul canvas o trascina dall'evento per aggiungere la prima.",
|
|
931
|
+
"emptyTitle": "Ancora nessuna email",
|
|
932
|
+
"hoursAfterEntering": "ore dopo l'ingresso nell'automazione",
|
|
933
|
+
"hoursAfterPrevious": "ore dopo l'email precedente",
|
|
934
|
+
"noTrigger": "Nessun evento",
|
|
935
|
+
"orphan": "fuori dalla sequenza",
|
|
936
|
+
"sent": "inviate {count}"
|
|
937
|
+
},
|
|
938
|
+
"fields": {
|
|
939
|
+
"exitTrigger": {
|
|
940
|
+
"label": "Evento di uscita"
|
|
941
|
+
},
|
|
942
|
+
"isActive": {
|
|
943
|
+
"help": "Spenta, tiene tutti fermi dove sono; le email in ritardo partono quando la riaccendi.",
|
|
944
|
+
"label": "Attiva"
|
|
945
|
+
},
|
|
946
|
+
"name": {
|
|
947
|
+
"error": "Il nome è obbligatorio",
|
|
948
|
+
"label": "Nome"
|
|
949
|
+
},
|
|
950
|
+
"replyTo": {
|
|
951
|
+
"error": "Inserisci un indirizzo email valido",
|
|
952
|
+
"label": "Rispondi a"
|
|
953
|
+
},
|
|
954
|
+
"trigger": {
|
|
955
|
+
"label": "Evento di ingresso",
|
|
956
|
+
"none": "— nessuno —"
|
|
957
|
+
}
|
|
958
|
+
},
|
|
959
|
+
"list": {
|
|
960
|
+
"columns": {
|
|
961
|
+
"active": "Attivi",
|
|
962
|
+
"completed": "Completati",
|
|
963
|
+
"name": "Nome",
|
|
964
|
+
"status": "Stato",
|
|
965
|
+
"steps": "Email",
|
|
966
|
+
"trigger": "Evento",
|
|
967
|
+
"unsubscribed": "Disiscritti"
|
|
968
|
+
},
|
|
969
|
+
"status": {
|
|
970
|
+
"off": "Spenta",
|
|
971
|
+
"on": "Attiva"
|
|
972
|
+
}
|
|
973
|
+
},
|
|
974
|
+
"recipients": {
|
|
975
|
+
"columns": {
|
|
976
|
+
"currentStep": "Prossima email",
|
|
977
|
+
"email": "Email",
|
|
978
|
+
"name": "Nome",
|
|
979
|
+
"nextSendAt": "In programma",
|
|
980
|
+
"sentCount": "Inviate",
|
|
981
|
+
"status": "Stato"
|
|
982
|
+
},
|
|
983
|
+
"empty": "Nessuno è ancora entrato in questa automazione.",
|
|
984
|
+
"status": {
|
|
985
|
+
"active": "Attivi",
|
|
986
|
+
"all": "Tutti",
|
|
987
|
+
"completed": "Completati",
|
|
988
|
+
"exited": "Usciti",
|
|
989
|
+
"failed": "Non riusciti",
|
|
990
|
+
"unsubscribed": "Disiscritti"
|
|
991
|
+
}
|
|
992
|
+
},
|
|
993
|
+
"step": {
|
|
994
|
+
"editTitle": "Modifica email",
|
|
995
|
+
"fields": {
|
|
996
|
+
"body": {
|
|
997
|
+
"error": "Il testo è obbligatorio",
|
|
998
|
+
"label": "Testo (Markdown)"
|
|
999
|
+
},
|
|
1000
|
+
"delayHours": {
|
|
1001
|
+
"label": "Attesa"
|
|
1002
|
+
},
|
|
1003
|
+
"subject": {
|
|
1004
|
+
"error": "L'oggetto è obbligatorio",
|
|
1005
|
+
"label": "Oggetto"
|
|
1006
|
+
}
|
|
1007
|
+
},
|
|
1008
|
+
"title": "Nuova email",
|
|
1009
|
+
"unsubscribeHint": "I link per disiscriversi vengono sempre aggiunti sotto il testo.",
|
|
1010
|
+
"variablesHint": "Variabili:"
|
|
1011
|
+
},
|
|
1012
|
+
"tabs": {
|
|
1013
|
+
"canvas": "Canvas",
|
|
1014
|
+
"recipients": "Destinatari"
|
|
1015
|
+
},
|
|
1016
|
+
"unsubscribe": {
|
|
1017
|
+
"alsoStopAll": "Interrompi anche tutte le email promozionali",
|
|
1018
|
+
"invalidBody": "Questo link non è valido.",
|
|
1019
|
+
"invalidTitle": "Link non valido",
|
|
1020
|
+
"loading": "Aggiornamento delle tue preferenze email in corso...",
|
|
1021
|
+
"stoppedAll": "Non riceverai più nessuna email promozionale da noi.",
|
|
1022
|
+
"stoppedAutomation": "Non riceverai altre email da {name}.",
|
|
1023
|
+
"title": "Disiscrizione completata"
|
|
1024
|
+
}
|
|
1025
|
+
},
|
|
909
1026
|
"entities": {
|
|
910
1027
|
"assistants": "{count, plural, =1 {Assistente} other {Assistenti}}",
|
|
911
1028
|
"companies": "{count, plural, =1 {Azienda} other {Aziende}}",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@carlonicora/nextjs-jsonapi",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.3.0",
|
|
4
4
|
"description": "Next.js JSON:API client with server/client support and caching",
|
|
5
5
|
"author": "Carlo Nicora",
|
|
6
6
|
"license": "GPL-3.0-or-later",
|
|
@@ -59,6 +59,11 @@
|
|
|
59
59
|
"import": "./dist/billing/index.mjs",
|
|
60
60
|
"require": "./dist/billing/index.js"
|
|
61
61
|
},
|
|
62
|
+
"./email-automations": {
|
|
63
|
+
"types": "./dist/email-automations/index.d.ts",
|
|
64
|
+
"import": "./dist/email-automations/index.mjs",
|
|
65
|
+
"require": "./dist/email-automations/index.js"
|
|
66
|
+
},
|
|
62
67
|
"./help": {
|
|
63
68
|
"types": "./dist/features/help/index.d.ts",
|
|
64
69
|
"import": "./dist/features/help/index.mjs",
|
|
@@ -118,6 +123,7 @@
|
|
|
118
123
|
"@stripe/react-stripe-js": ">=6.8.0",
|
|
119
124
|
"@stripe/stripe-js": ">=9.13.0",
|
|
120
125
|
"@tanstack/react-table": ">=8.0.0",
|
|
126
|
+
"@xyflow/react": ">=12.0.0",
|
|
121
127
|
"next": ">=16.4.0",
|
|
122
128
|
"next-intl": ">=4.0.0",
|
|
123
129
|
"react": ">=19.3.0",
|
|
@@ -134,6 +140,9 @@
|
|
|
134
140
|
"@tanstack/react-table": {
|
|
135
141
|
"optional": true
|
|
136
142
|
},
|
|
143
|
+
"@xyflow/react": {
|
|
144
|
+
"optional": true
|
|
145
|
+
},
|
|
137
146
|
"next-intl": {
|
|
138
147
|
"optional": true
|
|
139
148
|
},
|
|
@@ -220,6 +229,7 @@
|
|
|
220
229
|
"@vitejs/plugin-react": "^6.1.2",
|
|
221
230
|
"@vitest/coverage-v8": "^5.0.3",
|
|
222
231
|
"@vitest/ui": "^5.0.3",
|
|
232
|
+
"@xyflow/react": "^12.12.0",
|
|
223
233
|
"conventional-changelog-conventionalcommits": "9.3.1",
|
|
224
234
|
"eslint": "^9.39.5",
|
|
225
235
|
"eslint-config-prettier": "^10.1.8",
|
|
@@ -21,7 +21,7 @@ type FormFieldWrapperProps<T extends FieldValues> = {
|
|
|
21
21
|
* label built by the caller, so the label row stays one element.
|
|
22
22
|
*/
|
|
23
23
|
labelAction?: ReactNode;
|
|
24
|
-
description?:
|
|
24
|
+
description?: ReactNode;
|
|
25
25
|
isRequired?: boolean;
|
|
26
26
|
orientation?: "vertical" | "horizontal" | "responsive";
|
|
27
27
|
className?: string;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import { ReactNode } from "react";
|
|
3
4
|
import { Textarea } from "../../shadcnui";
|
|
4
5
|
import { cn } from "../../utils";
|
|
5
6
|
import { FormFieldWrapper } from "./FormFieldWrapper";
|
|
@@ -21,7 +22,7 @@ export function FormTextarea({
|
|
|
21
22
|
placeholder?: string;
|
|
22
23
|
className?: string;
|
|
23
24
|
testId?: string;
|
|
24
|
-
description?:
|
|
25
|
+
description?: ReactNode;
|
|
25
26
|
isRequired?: boolean;
|
|
26
27
|
textareaRef?: React.RefObject<HTMLTextAreaElement>;
|
|
27
28
|
}) {
|
package/src/core/index.ts
CHANGED
|
@@ -67,6 +67,14 @@ export * from "../features/content/content.module";
|
|
|
67
67
|
export * from "../features/content/data";
|
|
68
68
|
export * from "../features/how-to/HowToModule";
|
|
69
69
|
export * from "../features/how-to/data";
|
|
70
|
+
export * from "../features/email-automation/EmailAutomationModule";
|
|
71
|
+
export * from "../features/email-automation/EmailAutomationStepModule";
|
|
72
|
+
export * from "../features/email-automation/EmailAutomationEnrollmentModule";
|
|
73
|
+
export * from "../features/email-automation/EmailAutomationTriggerModule";
|
|
74
|
+
// Email automation data (models, services, interfaces, fields): server-safe, so a
|
|
75
|
+
// server page can wrap EmailAutomationService.findOne in cache(). The client-marked
|
|
76
|
+
// ./email-automations entry carries only components, contexts and hooks.
|
|
77
|
+
export * from "../features/email-automation/data";
|
|
70
78
|
export * from "../features/handbook/HandbookPageModule";
|
|
71
79
|
export * from "../features/handbook/HandbookSectionModule";
|
|
72
80
|
export * from "../features/handbook/HandbookThreadMessageModule";
|
|
@@ -41,6 +41,11 @@ export interface FoundationModuleDefinitions {
|
|
|
41
41
|
// Waitlist modules
|
|
42
42
|
Waitlist: ModuleWithPermissions;
|
|
43
43
|
WaitlistStats: ModuleWithPermissions;
|
|
44
|
+
// Email automation modules
|
|
45
|
+
EmailAutomation: ModuleWithPermissions;
|
|
46
|
+
EmailAutomationStep: ModuleWithPermissions;
|
|
47
|
+
EmailAutomationEnrollment: ModuleWithPermissions;
|
|
48
|
+
EmailAutomationTrigger: ModuleWithPermissions;
|
|
44
49
|
// Referral modules
|
|
45
50
|
Referral: ModuleWithPermissions;
|
|
46
51
|
ReferralStats: ModuleWithPermissions;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// Email automation components - separate entry point to avoid loading @xyflow/react globally
|
|
2
|
+
export * from "../features/email-automation/components";
|
|
3
|
+
export * from "../features/email-automation/contexts/EmailAutomationContext";
|
|
4
|
+
export * from "../features/email-automation/components/canvas/shared";
|
|
5
|
+
export * from "../features/email-automation/hooks";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { UsersIcon } from "lucide-react";
|
|
2
|
+
import { ModuleFactory } from "../../permissions";
|
|
3
|
+
import { EmailAutomationEnrollment } from "./data/EmailAutomationEnrollment";
|
|
4
|
+
|
|
5
|
+
export const EmailAutomationEnrollmentModule = (factory: ModuleFactory) =>
|
|
6
|
+
factory({
|
|
7
|
+
pageUrl: undefined,
|
|
8
|
+
name: "email-automation-enrollments",
|
|
9
|
+
model: EmailAutomationEnrollment,
|
|
10
|
+
icon: UsersIcon,
|
|
11
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { MailIcon } from "lucide-react";
|
|
2
|
+
import { createJsonApiInclusion } from "../../core";
|
|
3
|
+
import { ModuleFactory } from "../../permissions";
|
|
4
|
+
import { EmailAutomation } from "./data/EmailAutomation";
|
|
5
|
+
|
|
6
|
+
export const EmailAutomationModule = (factory: ModuleFactory) =>
|
|
7
|
+
factory({
|
|
8
|
+
moduleId: "3f6c1a2e-7b94-4d1e-8c55-9a0b2e4f6d71",
|
|
9
|
+
pageUrl: "/administration/email-automations",
|
|
10
|
+
name: "email-automations",
|
|
11
|
+
model: EmailAutomation,
|
|
12
|
+
icon: MailIcon,
|
|
13
|
+
inclusions: {
|
|
14
|
+
lists: {
|
|
15
|
+
fields: [
|
|
16
|
+
createJsonApiInclusion("email-automations", [
|
|
17
|
+
"name",
|
|
18
|
+
"trigger",
|
|
19
|
+
"exitTrigger",
|
|
20
|
+
"replyTo",
|
|
21
|
+
"isActive",
|
|
22
|
+
"stepCount",
|
|
23
|
+
"activeCount",
|
|
24
|
+
"completedCount",
|
|
25
|
+
"unsubscribedCount",
|
|
26
|
+
]),
|
|
27
|
+
],
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { MailIcon } from "lucide-react";
|
|
2
|
+
import { ModuleFactory } from "../../permissions";
|
|
3
|
+
import { EmailAutomationStep } from "./data/EmailAutomationStep";
|
|
4
|
+
|
|
5
|
+
export const EmailAutomationStepModule = (factory: ModuleFactory) =>
|
|
6
|
+
factory({
|
|
7
|
+
pageUrl: undefined,
|
|
8
|
+
name: "email-automation-steps",
|
|
9
|
+
model: EmailAutomationStep,
|
|
10
|
+
icon: MailIcon,
|
|
11
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ZapIcon } from "lucide-react";
|
|
2
|
+
import { ModuleFactory } from "../../permissions";
|
|
3
|
+
import { EmailAutomationTrigger } from "./data/EmailAutomationTrigger";
|
|
4
|
+
|
|
5
|
+
export const EmailAutomationTriggerModule = (factory: ModuleFactory) =>
|
|
6
|
+
factory({
|
|
7
|
+
pageUrl: undefined,
|
|
8
|
+
name: "email-automation-triggers",
|
|
9
|
+
model: EmailAutomationTrigger,
|
|
10
|
+
icon: ZapIcon,
|
|
11
|
+
});
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { render, screen, waitFor, within } from "@testing-library/react";
|
|
2
|
+
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
|
+
import { ModuleRegistry } from "../../../../core/registry/ModuleRegistry";
|
|
4
|
+
import { configureI18n } from "../../../../i18n";
|
|
5
|
+
import { EmailAutomationEditor } from "../forms/EmailAutomationEditor";
|
|
6
|
+
|
|
7
|
+
// Hoisted so the vi.mock factories below (which run before the module body)
|
|
8
|
+
// can close over them.
|
|
9
|
+
const mocks = vi.hoisted(() => ({
|
|
10
|
+
findTriggers: vi.fn(),
|
|
11
|
+
create: vi.fn(),
|
|
12
|
+
update: vi.fn(),
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
vi.mock("../../data/EmailAutomationService", () => ({
|
|
16
|
+
EmailAutomationService: {
|
|
17
|
+
findTriggers: mocks.findTriggers,
|
|
18
|
+
create: mocks.create,
|
|
19
|
+
update: mocks.update,
|
|
20
|
+
},
|
|
21
|
+
}));
|
|
22
|
+
|
|
23
|
+
// Base UI renders a closed Select's options nowhere in the DOM, so FormSelect
|
|
24
|
+
// is replaced by a stub that prints its label, its empty option and its
|
|
25
|
+
// values. Everything else in the barrel stays real.
|
|
26
|
+
vi.mock("../../../../components", async () => {
|
|
27
|
+
const actual = await vi.importActual<typeof import("../../../../components")>("../../../../components");
|
|
28
|
+
return {
|
|
29
|
+
...actual,
|
|
30
|
+
FormSelect: (props: {
|
|
31
|
+
id: string;
|
|
32
|
+
name?: string;
|
|
33
|
+
placeholder?: string;
|
|
34
|
+
allowEmpty?: boolean;
|
|
35
|
+
values: { id: string; text: string }[];
|
|
36
|
+
}) => (
|
|
37
|
+
<div data-testid={`form-select-${props.id}`}>
|
|
38
|
+
<span>{props.name}</span>
|
|
39
|
+
{props.allowEmpty && <span data-value="">{props.placeholder}</span>}
|
|
40
|
+
{props.values.map((value) => (
|
|
41
|
+
<span key={value.id} data-value={value.id}>
|
|
42
|
+
{value.text}
|
|
43
|
+
</span>
|
|
44
|
+
))}
|
|
45
|
+
</div>
|
|
46
|
+
),
|
|
47
|
+
};
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const TRIGGERS = [
|
|
51
|
+
{ id: "waitlist-confirmed", key: "waitlist-confirmed", label: "Waitlist confirmed" },
|
|
52
|
+
{ id: "registration-completed", key: "registration-completed", label: "Registration completed" },
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
// `Modules.EmailAutomation` resolves through the registry at render time.
|
|
56
|
+
beforeAll(() => {
|
|
57
|
+
ModuleRegistry.register("EmailAutomation", {
|
|
58
|
+
name: "email-automations",
|
|
59
|
+
pageUrl: "/administration/email-automations",
|
|
60
|
+
} as any);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
beforeEach(() => {
|
|
64
|
+
vi.clearAllMocks();
|
|
65
|
+
mocks.findTriggers.mockResolvedValue(TRIGGERS);
|
|
66
|
+
configureI18n({
|
|
67
|
+
useRouter: () => ({
|
|
68
|
+
push: vi.fn(),
|
|
69
|
+
replace: vi.fn(),
|
|
70
|
+
back: vi.fn(),
|
|
71
|
+
forward: vi.fn(),
|
|
72
|
+
refresh: vi.fn(),
|
|
73
|
+
prefetch: vi.fn(),
|
|
74
|
+
}),
|
|
75
|
+
useTranslations: () => (key: string) => key,
|
|
76
|
+
Link: ({ children }: any) => children,
|
|
77
|
+
usePathname: () => "/",
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe("EmailAutomationEditor", () => {
|
|
82
|
+
it("renders the five fields by their labels", async () => {
|
|
83
|
+
render(<EmailAutomationEditor forceShow dialogOpen />);
|
|
84
|
+
|
|
85
|
+
expect(await screen.findByText("emailAutomations.fields.name.label")).toBeInTheDocument();
|
|
86
|
+
expect(screen.getByText("emailAutomations.fields.trigger.label")).toBeInTheDocument();
|
|
87
|
+
expect(screen.getByText("emailAutomations.fields.exitTrigger.label")).toBeInTheDocument();
|
|
88
|
+
expect(screen.getByText("emailAutomations.fields.replyTo.label")).toBeInTheDocument();
|
|
89
|
+
expect(screen.getByText("emailAutomations.fields.isActive.label")).toBeInTheDocument();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("lists the none option plus the trigger labels returned by the API", async () => {
|
|
93
|
+
render(<EmailAutomationEditor forceShow dialogOpen />);
|
|
94
|
+
|
|
95
|
+
const triggerSelect = await screen.findByTestId("form-select-trigger");
|
|
96
|
+
await waitFor(() => expect(within(triggerSelect).getByText("Waitlist confirmed")).toBeInTheDocument());
|
|
97
|
+
|
|
98
|
+
expect(mocks.findTriggers).toHaveBeenCalled();
|
|
99
|
+
expect(within(triggerSelect).getByText("emailAutomations.fields.trigger.none")).toBeInTheDocument();
|
|
100
|
+
expect(within(triggerSelect).getByText("Registration completed")).toBeInTheDocument();
|
|
101
|
+
// The option values are the trigger keys, the visible text the plain label.
|
|
102
|
+
expect(within(triggerSelect).getByText("Waitlist confirmed")).toHaveAttribute("data-value", "waitlist-confirmed");
|
|
103
|
+
});
|
|
104
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useTranslations } from "next-intl";
|
|
4
|
+
import { useState } from "react";
|
|
5
|
+
import { errorToast } from "../../../../components";
|
|
6
|
+
import { Switch, Tooltip, TooltipContent, TooltipTrigger } from "../../../../shadcnui";
|
|
7
|
+
import { useEmailAutomation } from "../../contexts/EmailAutomationContext";
|
|
8
|
+
import { EmailAutomationService } from "../../data/EmailAutomationService";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Inline on/off for the automation page header, so pausing never needs the
|
|
12
|
+
* editor sheet. Off holds every enrolment in place (spec § "Sending" 3).
|
|
13
|
+
*/
|
|
14
|
+
export function EmailAutomationActiveSwitch() {
|
|
15
|
+
const t = useTranslations();
|
|
16
|
+
const { automation, reload } = useEmailAutomation();
|
|
17
|
+
const [saving, setSaving] = useState<boolean>(false);
|
|
18
|
+
|
|
19
|
+
if (!automation) return null;
|
|
20
|
+
|
|
21
|
+
const toggle = async (checked: boolean) => {
|
|
22
|
+
setSaving(true);
|
|
23
|
+
try {
|
|
24
|
+
await EmailAutomationService.update({
|
|
25
|
+
id: automation.id,
|
|
26
|
+
name: automation.name,
|
|
27
|
+
trigger: automation.trigger,
|
|
28
|
+
exitTrigger: automation.exitTrigger,
|
|
29
|
+
replyTo: automation.replyTo,
|
|
30
|
+
isActive: checked,
|
|
31
|
+
});
|
|
32
|
+
await reload();
|
|
33
|
+
} catch (error) {
|
|
34
|
+
errorToast({ title: t("common.errors.error"), error });
|
|
35
|
+
} finally {
|
|
36
|
+
setSaving(false);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<Tooltip>
|
|
42
|
+
<TooltipTrigger render={<label className="flex items-center gap-x-2" />}>
|
|
43
|
+
<Switch checked={automation.isActive} onCheckedChange={(checked) => void toggle(checked)} disabled={saving} />
|
|
44
|
+
<span className="text-xs">{t("emailAutomations.fields.isActive.label")}</span>
|
|
45
|
+
</TooltipTrigger>
|
|
46
|
+
<TooltipContent className="max-w-xs">{t("emailAutomations.fields.isActive.help")}</TooltipContent>
|
|
47
|
+
</Tooltip>
|
|
48
|
+
);
|
|
49
|
+
}
|