@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.
Files changed (173) hide show
  1. package/dist/{BlockNoteEditor-KO6YFNIU.js → BlockNoteEditor-UJ6363LC.js} +19 -19
  2. package/dist/{BlockNoteEditor-KO6YFNIU.js.map → BlockNoteEditor-UJ6363LC.js.map} +1 -1
  3. package/dist/{BlockNoteEditor-66FJUDL4.mjs → BlockNoteEditor-WTSQANKI.mjs} +4 -4
  4. package/dist/{BlockNoteViewer-LK22AE7Z.js → BlockNoteViewer-EL4JFB6P.js} +3 -3
  5. package/dist/{BlockNoteViewer-LK22AE7Z.js.map → BlockNoteViewer-EL4JFB6P.js.map} +1 -1
  6. package/dist/{BlockNoteViewer-SUK7OMWH.mjs → BlockNoteViewer-JVKWJZ6J.mjs} +2 -2
  7. package/dist/auth.interface-CSzJ1oP1.d.ts +64 -0
  8. package/dist/auth.interface-DD4uQv2K.d.mts +64 -0
  9. package/dist/billing/index.js +441 -441
  10. package/dist/billing/index.mjs +3 -3
  11. package/dist/{chunk-SZQNUGDG.js → chunk-3BAM23VA.js} +1005 -929
  12. package/dist/chunk-3BAM23VA.js.map +1 -0
  13. package/dist/{chunk-OKUGTXDN.mjs → chunk-6WU74VFU.mjs} +121 -3
  14. package/dist/chunk-6WU74VFU.mjs.map +1 -0
  15. package/dist/{chunk-VVJDWQEI.js → chunk-CRI4T47Y.js} +790 -179
  16. package/dist/chunk-CRI4T47Y.js.map +1 -0
  17. package/dist/{chunk-TO6IYGML.js → chunk-EO75CV76.js} +126 -8
  18. package/dist/chunk-EO75CV76.js.map +1 -0
  19. package/dist/{chunk-KL7CAQP3.mjs → chunk-FNTQQFLH.mjs} +2 -2
  20. package/dist/{chunk-OCJEXAGE.mjs → chunk-JQWI62NV.mjs} +617 -6
  21. package/dist/chunk-JQWI62NV.mjs.map +1 -0
  22. package/dist/{chunk-5JADF7ZA.mjs → chunk-MIZTWBPX.mjs} +171 -95
  23. package/dist/chunk-MIZTWBPX.mjs.map +1 -0
  24. package/dist/{chunk-XLVRAHUK.mjs → chunk-QKZUE5OF.mjs} +2 -2
  25. package/dist/{chunk-67NJY7SM.js → chunk-QQQE3N2X.js} +3 -3
  26. package/dist/{chunk-67NJY7SM.js.map → chunk-QQQE3N2X.js.map} +1 -1
  27. package/dist/{chunk-XCHXOCEJ.js → chunk-XGTQWBDJ.js} +4 -4
  28. package/dist/{chunk-XCHXOCEJ.js.map → chunk-XGTQWBDJ.js.map} +1 -1
  29. package/dist/client/index.d.mts +6 -39
  30. package/dist/client/index.d.ts +6 -39
  31. package/dist/client/index.js +4 -4
  32. package/dist/client/index.mjs +3 -3
  33. package/dist/components/index.d.mts +19 -5
  34. package/dist/components/index.d.ts +19 -5
  35. package/dist/components/index.js +6 -4
  36. package/dist/components/index.js.map +1 -1
  37. package/dist/components/index.mjs +5 -3
  38. package/dist/contexts/index.js +4 -4
  39. package/dist/contexts/index.mjs +3 -3
  40. package/dist/core/index.d.mts +214 -8
  41. package/dist/core/index.d.ts +214 -8
  42. package/dist/core/index.js +30 -2
  43. package/dist/core/index.js.map +1 -1
  44. package/dist/core/index.mjs +29 -1
  45. package/dist/email-automation-enrollment.fields-BZvF14qx.d.mts +82 -0
  46. package/dist/email-automation-enrollment.fields-Cp3VMzFq.d.ts +82 -0
  47. package/dist/email-automations/index.css +30 -0
  48. package/dist/email-automations/index.css.map +1 -0
  49. package/dist/email-automations/index.d.mts +303 -0
  50. package/dist/email-automations/index.d.ts +303 -0
  51. package/dist/email-automations/index.js +1705 -0
  52. package/dist/email-automations/index.js.map +1 -0
  53. package/dist/email-automations/index.mjs +1705 -0
  54. package/dist/email-automations/index.mjs.map +1 -0
  55. package/dist/features/analytics/index.js +122 -122
  56. package/dist/features/analytics/index.mjs +4 -4
  57. package/dist/features/analytics/server.js +4 -4
  58. package/dist/features/analytics/server.mjs +2 -2
  59. package/dist/features/help/index.js +37 -37
  60. package/dist/features/help/index.mjs +3 -3
  61. package/dist/features/tokenusage/index.js +125 -125
  62. package/dist/features/tokenusage/index.mjs +4 -4
  63. package/dist/index.d.mts +5 -4
  64. package/dist/index.d.ts +5 -4
  65. package/dist/index.js +41 -3
  66. package/dist/index.js.map +1 -1
  67. package/dist/index.mjs +41 -3
  68. package/dist/{s3.service-C-4YpEMu.d.ts → s3.service-CY-jgOAM.d.ts} +1 -1
  69. package/dist/{s3.service-dVj-nxMl.d.mts → s3.service-Sri0QP7Z.d.mts} +1 -1
  70. package/dist/server/index.d.mts +2 -2
  71. package/dist/server/index.d.ts +2 -2
  72. package/dist/server/index.js +3 -3
  73. package/dist/server/index.mjs +2 -2
  74. package/dist/types-70acd-8C.d.mts +39 -0
  75. package/dist/types-l8SFleXi.d.ts +39 -0
  76. package/dist/{waitlist.config-kPfjImle.d.mts → waitlist.config-Cagmgm7-.d.mts} +7 -0
  77. package/dist/{waitlist.config-kPfjImle.d.ts → waitlist.config-Cagmgm7-.d.ts} +7 -0
  78. package/messages/en.json +132 -1
  79. package/messages/it.json +132 -1
  80. package/package.json +11 -1
  81. package/scripts/validate-messages.mjs +1 -0
  82. package/src/components/forms/FormFieldWrapper.tsx +1 -1
  83. package/src/components/forms/FormTextarea.tsx +2 -1
  84. package/src/components/index.ts +1 -0
  85. package/src/core/index.ts +8 -0
  86. package/src/core/registry/ModuleRegistry.ts +5 -0
  87. package/src/email-automations/index.ts +5 -0
  88. package/src/features/auth/components/buttons/GoogleSignInButton.tsx +8 -4
  89. package/src/features/auth/components/forms/Register.tsx +20 -8
  90. package/src/features/auth/data/auth.interface.ts +2 -0
  91. package/src/features/auth/data/auth.ts +10 -0
  92. package/src/features/email-automation/EmailAutomationEnrollmentModule.ts +11 -0
  93. package/src/features/email-automation/EmailAutomationModule.ts +30 -0
  94. package/src/features/email-automation/EmailAutomationStepModule.ts +11 -0
  95. package/src/features/email-automation/EmailAutomationTriggerModule.ts +11 -0
  96. package/src/features/email-automation/components/__tests__/EmailAutomationEditor.spec.tsx +104 -0
  97. package/src/features/email-automation/components/canvas/EmailAutomationActiveSwitch.tsx +49 -0
  98. package/src/features/email-automation/components/canvas/EmailAutomationCanvas.tsx +474 -0
  99. package/src/features/email-automation/components/canvas/EmailAutomationCanvasInspector.tsx +89 -0
  100. package/src/features/email-automation/components/canvas/EmailAutomationCanvasTab.tsx +32 -0
  101. package/src/features/email-automation/components/canvas/__tests__/buildCanvasGraph.spec.ts +67 -0
  102. package/src/features/email-automation/components/canvas/buildCanvasGraph.ts +141 -0
  103. package/src/features/email-automation/components/canvas/edges/DelayEdge.tsx +68 -0
  104. package/src/features/email-automation/components/canvas/nodes/EmailStepNode.tsx +43 -0
  105. package/src/features/email-automation/components/canvas/nodes/TriggerNode.tsx +23 -0
  106. package/src/features/email-automation/components/canvas/shared/CanvasToolbar.tsx +53 -0
  107. package/src/features/email-automation/components/canvas/shared/EmptyCanvas.tsx +11 -0
  108. package/src/features/email-automation/components/canvas/shared/__tests__/autoLayout.spec.ts +42 -0
  109. package/src/features/email-automation/components/canvas/shared/__tests__/longPress.spec.ts +51 -0
  110. package/src/features/email-automation/components/canvas/shared/autoLayout.ts +55 -0
  111. package/src/features/email-automation/components/canvas/shared/index.ts +4 -0
  112. package/src/features/email-automation/components/canvas/shared/longPress.ts +57 -0
  113. package/src/features/email-automation/components/containers/EmailAutomationContainer.tsx +80 -0
  114. package/src/features/email-automation/components/containers/EmailAutomationsListContainer.tsx +27 -0
  115. package/src/features/email-automation/components/forms/EmailAutomationEditor.tsx +153 -0
  116. package/src/features/email-automation/components/forms/EmailStepEditor.tsx +151 -0
  117. package/src/features/email-automation/components/index.ts +15 -0
  118. package/src/features/email-automation/components/lists/EmailAutomationRecipientsList.tsx +90 -0
  119. package/src/features/email-automation/components/lists/EmailAutomationsList.tsx +76 -0
  120. package/src/features/email-automation/components/unsubscribe/EmailUnsubscribe.tsx +99 -0
  121. package/src/features/email-automation/components/unsubscribe/__tests__/EmailUnsubscribe.spec.tsx +55 -0
  122. package/src/features/email-automation/contexts/EmailAutomationContext.tsx +175 -0
  123. package/src/features/email-automation/data/EmailAutomation.ts +87 -0
  124. package/src/features/email-automation/data/EmailAutomationEnrollment.ts +105 -0
  125. package/src/features/email-automation/data/EmailAutomationEnrollmentInterface.ts +18 -0
  126. package/src/features/email-automation/data/EmailAutomationEnrollmentService.ts +31 -0
  127. package/src/features/email-automation/data/EmailAutomationInterface.ts +22 -0
  128. package/src/features/email-automation/data/EmailAutomationService.ts +106 -0
  129. package/src/features/email-automation/data/EmailAutomationStep.ts +104 -0
  130. package/src/features/email-automation/data/EmailAutomationStepInterface.ts +23 -0
  131. package/src/features/email-automation/data/EmailAutomationStepService.ts +80 -0
  132. package/src/features/email-automation/data/EmailAutomationTrigger.ts +28 -0
  133. package/src/features/email-automation/data/EmailAutomationTriggerInterface.ts +6 -0
  134. package/src/features/email-automation/data/__tests__/email-automation-models.spec.ts +103 -0
  135. package/src/features/email-automation/data/email-automation-enrollment.fields.ts +8 -0
  136. package/src/features/email-automation/data/email-automation.fields.ts +9 -0
  137. package/src/features/email-automation/data/index.ts +13 -0
  138. package/src/features/email-automation/hooks/index.ts +2 -0
  139. package/src/features/email-automation/hooks/useEmailAutomationEnrollmentTableStructure.tsx +126 -0
  140. package/src/features/email-automation/hooks/useEmailAutomationTableStructure.tsx +119 -0
  141. package/src/features/email-automation/index.ts +5 -0
  142. package/src/features/signup-source/SignupSourceCapture.tsx +27 -0
  143. package/src/features/signup-source/__tests__/signup-source.spec.ts +161 -0
  144. package/src/features/signup-source/index.ts +9 -0
  145. package/src/features/signup-source/signup-source.ts +135 -0
  146. package/src/features/waitlist/components/forms/WaitlistForm.tsx +2 -0
  147. package/src/features/waitlist/components/forms/WaitlistQuestionnaireRenderer.tsx +15 -13
  148. package/src/features/waitlist/components/lists/WaitlistList.tsx +60 -22
  149. package/src/features/waitlist/components/sections/WaitlistHeroSection.tsx +6 -5
  150. package/src/features/waitlist/config/waitlist.config.ts +23 -0
  151. package/src/features/waitlist/data/Waitlist.ts +51 -3
  152. package/src/features/waitlist/data/WaitlistInterface.ts +9 -1
  153. package/src/features/waitlist/data/WaitlistService.ts +3 -2
  154. package/src/features/waitlist/data/__tests__/Waitlist.spec.ts +82 -0
  155. package/src/features/waitlist/data/__tests__/WaitlistService.spec.ts +48 -0
  156. package/src/features/waitlist/data/waitlist.fields.ts +1 -0
  157. package/src/features/waitlist/hooks/__tests__/formatSignupSource.spec.ts +24 -0
  158. package/src/features/waitlist/hooks/formatSignupSource.ts +14 -0
  159. package/src/features/waitlist/hooks/useWaitlistTableStructure.tsx +26 -14
  160. package/src/hooks/__tests__/usePageTracker.spec.tsx +16 -10
  161. package/src/index.ts +3 -0
  162. package/dist/auth.interface-CeFCz9NU.d.mts +0 -34
  163. package/dist/auth.interface-D1R7VVky.d.ts +0 -34
  164. package/dist/chunk-5JADF7ZA.mjs.map +0 -1
  165. package/dist/chunk-OCJEXAGE.mjs.map +0 -1
  166. package/dist/chunk-OKUGTXDN.mjs.map +0 -1
  167. package/dist/chunk-SZQNUGDG.js.map +0 -1
  168. package/dist/chunk-TO6IYGML.js.map +0 -1
  169. package/dist/chunk-VVJDWQEI.js.map +0 -1
  170. /package/dist/{BlockNoteEditor-66FJUDL4.mjs.map → BlockNoteEditor-WTSQANKI.mjs.map} +0 -0
  171. /package/dist/{BlockNoteViewer-SUK7OMWH.mjs.map → BlockNoteViewer-JVKWJZ6J.mjs.map} +0 -0
  172. /package/dist/{chunk-KL7CAQP3.mjs.map → chunk-FNTQQFLH.mjs.map} +0 -0
  173. /package/dist/{chunk-XLVRAHUK.mjs.map → chunk-QKZUE5OF.mjs.map} +0 -0
@@ -0,0 +1,106 @@
1
+ import { AbstractService, EndpointCreator, HttpMethod, Modules, NextRef, PreviousRef } from "../../../core";
2
+ import { EmailAutomationEnrollmentInterface } from "./EmailAutomationEnrollmentInterface";
3
+ import { EmailAutomationInput, EmailAutomationInterface } from "./EmailAutomationInterface";
4
+ import { EmailAutomationTriggerInterface } from "./EmailAutomationTriggerInterface";
5
+
6
+ export class EmailAutomationService extends AbstractService {
7
+ static async findMany(params: { next?: NextRef; previous?: PreviousRef } = {}): Promise<EmailAutomationInterface[]> {
8
+ const endpoint = new EndpointCreator({ endpoint: Modules.EmailAutomation });
9
+
10
+ if (Modules.EmailAutomation.inclusions?.lists?.fields)
11
+ endpoint.limitToFields(Modules.EmailAutomation.inclusions.lists.fields);
12
+ if (Modules.EmailAutomation.inclusions?.lists?.types)
13
+ endpoint.limitToType(Modules.EmailAutomation.inclusions.lists.types);
14
+
15
+ return this.callApi<EmailAutomationInterface[]>({
16
+ type: Modules.EmailAutomation,
17
+ method: HttpMethod.GET,
18
+ endpoint: endpoint.generate(),
19
+ next: params.next,
20
+ previous: params.previous,
21
+ });
22
+ }
23
+
24
+ static async findOne(params: { id: string }): Promise<EmailAutomationInterface> {
25
+ return this.callApi<EmailAutomationInterface>({
26
+ type: Modules.EmailAutomation,
27
+ method: HttpMethod.GET,
28
+ endpoint: new EndpointCreator({ endpoint: Modules.EmailAutomation, id: params.id }).generate(),
29
+ });
30
+ }
31
+
32
+ static async create(params: EmailAutomationInput): Promise<EmailAutomationInterface> {
33
+ return this.callApi<EmailAutomationInterface>({
34
+ type: Modules.EmailAutomation,
35
+ method: HttpMethod.POST,
36
+ endpoint: new EndpointCreator({ endpoint: Modules.EmailAutomation }).generate(),
37
+ input: params,
38
+ });
39
+ }
40
+
41
+ static async update(params: EmailAutomationInput): Promise<EmailAutomationInterface> {
42
+ return this.callApi<EmailAutomationInterface>({
43
+ type: Modules.EmailAutomation,
44
+ method: HttpMethod.PATCH,
45
+ endpoint: new EndpointCreator({ endpoint: Modules.EmailAutomation, id: params.id }).generate(),
46
+ input: params,
47
+ });
48
+ }
49
+
50
+ static async delete(params: { id: string }): Promise<void> {
51
+ await this.callApi({
52
+ type: Modules.EmailAutomation,
53
+ method: HttpMethod.DELETE,
54
+ endpoint: new EndpointCreator({ endpoint: Modules.EmailAutomation, id: params.id }).generate(),
55
+ });
56
+ }
57
+
58
+ /**
59
+ * GET email-automations/triggers: the triggers the app declared to the API.
60
+ */
61
+ static async findTriggers(): Promise<EmailAutomationTriggerInterface[]> {
62
+ return this.callApi<EmailAutomationTriggerInterface[]>({
63
+ type: Modules.EmailAutomationTrigger,
64
+ method: HttpMethod.GET,
65
+ endpoint: new EndpointCreator({ endpoint: Modules.EmailAutomation, id: "triggers" }).generate(),
66
+ });
67
+ }
68
+
69
+ /**
70
+ * POST email-automations/:id/tidy: clears every manual step position. Replies 204.
71
+ */
72
+ static async tidy(params: { id: string }): Promise<void> {
73
+ await this.callApi({
74
+ type: Modules.EmailAutomation,
75
+ method: HttpMethod.POST,
76
+ endpoint: new EndpointCreator({
77
+ endpoint: Modules.EmailAutomation,
78
+ id: params.id,
79
+ childEndpoint: "tidy",
80
+ }).generate(),
81
+ });
82
+ }
83
+
84
+ static async findEnrollments(params: {
85
+ automationId: string;
86
+ status?: string;
87
+ next?: NextRef;
88
+ previous?: PreviousRef;
89
+ }): Promise<EmailAutomationEnrollmentInterface[]> {
90
+ const endpoint = new EndpointCreator({
91
+ endpoint: Modules.EmailAutomation,
92
+ id: params.automationId,
93
+ childEndpoint: Modules.EmailAutomationEnrollment,
94
+ });
95
+
96
+ if (params.status) endpoint.addAdditionalParam("status", params.status);
97
+
98
+ return this.callApi<EmailAutomationEnrollmentInterface[]>({
99
+ type: Modules.EmailAutomationEnrollment,
100
+ method: HttpMethod.GET,
101
+ endpoint: endpoint.generate(),
102
+ next: params.next,
103
+ previous: params.previous,
104
+ });
105
+ }
106
+ }
@@ -0,0 +1,104 @@
1
+ import { AbstractApiData, JsonApiHydratedDataInterface, Modules } from "../../../core";
2
+ import { EmailAutomationInterface } from "./EmailAutomationInterface";
3
+ import { EmailAutomationStepInput, EmailAutomationStepInterface } from "./EmailAutomationStepInterface";
4
+
5
+ export class EmailAutomationStep extends AbstractApiData implements EmailAutomationStepInterface {
6
+ private _subject?: string;
7
+ private _body?: string;
8
+ private _delayHours?: number;
9
+ private _positionX?: number;
10
+ private _positionY?: number;
11
+ private _sentCount?: number;
12
+ private _automation?: EmailAutomationInterface;
13
+ private _next?: EmailAutomationStepInterface;
14
+
15
+ get subject(): string {
16
+ return this._subject ?? "";
17
+ }
18
+
19
+ get body(): string {
20
+ return this._body ?? "";
21
+ }
22
+
23
+ get delayHours(): number {
24
+ return this._delayHours ?? 0;
25
+ }
26
+
27
+ get positionX(): number | undefined {
28
+ return this._positionX;
29
+ }
30
+
31
+ get positionY(): number | undefined {
32
+ return this._positionY;
33
+ }
34
+
35
+ get sentCount(): number {
36
+ return this._sentCount ?? 0;
37
+ }
38
+
39
+ get automation(): EmailAutomationInterface | undefined {
40
+ return this._automation;
41
+ }
42
+
43
+ get next(): EmailAutomationStepInterface | undefined {
44
+ return this._next;
45
+ }
46
+
47
+ rehydrate(data: JsonApiHydratedDataInterface): this {
48
+ super.rehydrate(data);
49
+
50
+ this._subject = data.jsonApi.attributes.subject;
51
+ this._body = data.jsonApi.attributes.body;
52
+ this._delayHours = data.jsonApi.attributes.delayHours;
53
+ this._positionX = data.jsonApi.attributes.positionX ?? undefined;
54
+ this._positionY = data.jsonApi.attributes.positionY ?? undefined;
55
+ this._sentCount = data.jsonApi.attributes.sentCount;
56
+
57
+ this._automation = this._readIncluded(data, "automation", Modules.EmailAutomation) as EmailAutomationInterface;
58
+ this._next = this._readIncluded(data, "next", Modules.EmailAutomationStep) as EmailAutomationStepInterface;
59
+
60
+ return this;
61
+ }
62
+
63
+ createJsonApi(data: EmailAutomationStepInput) {
64
+ const response: any = {
65
+ data: {
66
+ type: Modules.EmailAutomationStep.name,
67
+ id: data.id,
68
+ attributes: {},
69
+ meta: {},
70
+ relationships: {},
71
+ },
72
+ included: [],
73
+ };
74
+
75
+ if (data.subject !== undefined) response.data.attributes.subject = data.subject;
76
+ if (data.body !== undefined) response.data.attributes.body = data.body;
77
+ if (data.delayHours !== undefined) response.data.attributes.delayHours = data.delayHours;
78
+ if (data.positionX !== undefined) response.data.attributes.positionX = data.positionX;
79
+ if (data.positionY !== undefined) response.data.attributes.positionY = data.positionY;
80
+
81
+ if (data.automationId) {
82
+ response.data.relationships.automation = {
83
+ data: {
84
+ type: Modules.EmailAutomation.name,
85
+ id: data.automationId,
86
+ },
87
+ };
88
+ }
89
+
90
+ return response;
91
+ }
92
+
93
+ /**
94
+ * Body of POST email-automation-steps/:stepId/next: the step the chain continues to.
95
+ */
96
+ createNextJsonApi(params: { nextStepId: string }) {
97
+ return {
98
+ data: {
99
+ type: Modules.EmailAutomationStep.name,
100
+ id: params.nextStepId,
101
+ },
102
+ };
103
+ }
104
+ }
@@ -0,0 +1,23 @@
1
+ import { ApiDataInterface } from "../../../core";
2
+ import { EmailAutomationInterface } from "./EmailAutomationInterface";
3
+
4
+ export type EmailAutomationStepInput = {
5
+ id: string;
6
+ automationId: string;
7
+ subject: string;
8
+ body: string;
9
+ delayHours?: number;
10
+ positionX?: number;
11
+ positionY?: number;
12
+ };
13
+
14
+ export interface EmailAutomationStepInterface extends ApiDataInterface {
15
+ get subject(): string;
16
+ get body(): string;
17
+ get delayHours(): number;
18
+ get positionX(): number | undefined;
19
+ get positionY(): number | undefined;
20
+ get sentCount(): number;
21
+ get automation(): EmailAutomationInterface | undefined;
22
+ get next(): EmailAutomationStepInterface | undefined;
23
+ }
@@ -0,0 +1,80 @@
1
+ import { AbstractService, EndpointCreator, HttpMethod, Modules } from "../../../core";
2
+ import { EmailAutomationStep } from "./EmailAutomationStep";
3
+ import { EmailAutomationStepInput, EmailAutomationStepInterface } from "./EmailAutomationStepInterface";
4
+
5
+ export class EmailAutomationStepService extends AbstractService {
6
+ static async findByAutomation(params: { automationId: string }): Promise<EmailAutomationStepInterface[]> {
7
+ const endpoint = new EndpointCreator({
8
+ endpoint: Modules.EmailAutomation,
9
+ id: params.automationId,
10
+ childEndpoint: Modules.EmailAutomationStep,
11
+ });
12
+
13
+ endpoint.addAdditionalParam("fetchAll", "true");
14
+
15
+ return this.callApi<EmailAutomationStepInterface[]>({
16
+ type: Modules.EmailAutomationStep,
17
+ method: HttpMethod.GET,
18
+ endpoint: endpoint.generate(),
19
+ });
20
+ }
21
+
22
+ static async create(params: EmailAutomationStepInput): Promise<EmailAutomationStepInterface> {
23
+ return this.callApi<EmailAutomationStepInterface>({
24
+ type: Modules.EmailAutomationStep,
25
+ method: HttpMethod.POST,
26
+ endpoint: new EndpointCreator({ endpoint: Modules.EmailAutomationStep }).generate(),
27
+ input: params,
28
+ });
29
+ }
30
+
31
+ static async update(params: EmailAutomationStepInput): Promise<EmailAutomationStepInterface> {
32
+ return this.callApi<EmailAutomationStepInterface>({
33
+ type: Modules.EmailAutomationStep,
34
+ method: HttpMethod.PATCH,
35
+ endpoint: new EndpointCreator({ endpoint: Modules.EmailAutomationStep, id: params.id }).generate(),
36
+ input: params,
37
+ });
38
+ }
39
+
40
+ static async delete(params: { id: string }): Promise<void> {
41
+ await this.callApi({
42
+ type: Modules.EmailAutomationStep,
43
+ method: HttpMethod.DELETE,
44
+ endpoint: new EndpointCreator({ endpoint: Modules.EmailAutomationStep, id: params.id }).generate(),
45
+ });
46
+ }
47
+
48
+ /**
49
+ * POST email-automation-steps/:stepId/next: continues the chain from stepId to nextStepId.
50
+ */
51
+ static async setNext(params: { stepId: string; nextStepId: string }): Promise<EmailAutomationStepInterface> {
52
+ const step = new EmailAutomationStep();
53
+ return this.callApi<EmailAutomationStepInterface>({
54
+ type: Modules.EmailAutomationStep,
55
+ method: HttpMethod.POST,
56
+ endpoint: new EndpointCreator({
57
+ endpoint: Modules.EmailAutomationStep,
58
+ id: params.stepId,
59
+ childEndpoint: "next",
60
+ }).generate(),
61
+ input: step.createNextJsonApi({ nextStepId: params.nextStepId }),
62
+ overridesJsonApiCreation: true,
63
+ });
64
+ }
65
+
66
+ /**
67
+ * DELETE email-automation-steps/:stepId/next: the step becomes the end of the chain.
68
+ */
69
+ static async clearNext(params: { stepId: string }): Promise<void> {
70
+ await this.callApi({
71
+ type: Modules.EmailAutomationStep,
72
+ method: HttpMethod.DELETE,
73
+ endpoint: new EndpointCreator({
74
+ endpoint: Modules.EmailAutomationStep,
75
+ id: params.stepId,
76
+ childEndpoint: "next",
77
+ }).generate(),
78
+ });
79
+ }
80
+ }
@@ -0,0 +1,28 @@
1
+ import { AbstractApiData, JsonApiHydratedDataInterface } from "../../../core";
2
+ import { EmailAutomationTriggerInterface } from "./EmailAutomationTriggerInterface";
3
+
4
+ export class EmailAutomationTrigger extends AbstractApiData implements EmailAutomationTriggerInterface {
5
+ private _key?: string;
6
+ private _label?: string;
7
+
8
+ get key(): string {
9
+ return this._key ?? "";
10
+ }
11
+
12
+ get label(): string {
13
+ return this._label ?? "";
14
+ }
15
+
16
+ rehydrate(data: JsonApiHydratedDataInterface): this {
17
+ super.rehydrate(data);
18
+
19
+ this._key = data.jsonApi.attributes.key;
20
+ this._label = data.jsonApi.attributes.label;
21
+
22
+ return this;
23
+ }
24
+
25
+ createJsonApi(_data?: any): any {
26
+ throw new Error("EmailAutomationTrigger is declared by the API and is read-only");
27
+ }
28
+ }
@@ -0,0 +1,6 @@
1
+ import { ApiDataInterface } from "../../../core";
2
+
3
+ export interface EmailAutomationTriggerInterface extends ApiDataInterface {
4
+ get key(): string;
5
+ get label(): string;
6
+ }
@@ -0,0 +1,103 @@
1
+ import { beforeAll, describe, expect, it } from "vitest";
2
+ import { ModuleRegistry } from "../../../../core/registry/ModuleRegistry";
3
+ import { ModuleFactory, ModuleWithPermissions } from "../../../../permissions/types";
4
+ import { EmailAutomationEnrollmentModule } from "../../EmailAutomationEnrollmentModule";
5
+ import { EmailAutomationModule } from "../../EmailAutomationModule";
6
+ import { EmailAutomationStepModule } from "../../EmailAutomationStepModule";
7
+ import { EmailAutomationTriggerModule } from "../../EmailAutomationTriggerModule";
8
+ import { EmailAutomation } from "../EmailAutomation";
9
+ import { EMAIL_AUTOMATION_UNSUBSCRIBE_TYPE, EmailAutomationEnrollment } from "../EmailAutomationEnrollment";
10
+ import { EmailAutomationStep } from "../EmailAutomationStep";
11
+
12
+ // createJsonApi reads Modules.X.name (resolved lazily via ModuleRegistry), so
13
+ // the modules must be registered before a model serialises — mirrors the
14
+ // tokenusage-admin-models.spec setup convention in this package.
15
+ const moduleFactory: ModuleFactory = (params) =>
16
+ ({
17
+ pageUrl: params.pageUrl,
18
+ name: params.name,
19
+ model: params.model,
20
+ }) as ModuleWithPermissions;
21
+
22
+ beforeAll(() => {
23
+ ModuleRegistry.register("EmailAutomation", EmailAutomationModule(moduleFactory));
24
+ ModuleRegistry.register("EmailAutomationStep", EmailAutomationStepModule(moduleFactory));
25
+ ModuleRegistry.register("EmailAutomationEnrollment", EmailAutomationEnrollmentModule(moduleFactory));
26
+ ModuleRegistry.register("EmailAutomationTrigger", EmailAutomationTriggerModule(moduleFactory));
27
+ });
28
+
29
+ const hydrate = (attributes: Record<string, unknown>) =>
30
+ ({ jsonApi: { id: "x", type: "t", attributes }, included: [] }) as any;
31
+
32
+ describe("email automation models", () => {
33
+ it("parses the enrollment datetimes into Date, not the wire string", () => {
34
+ const m = new EmailAutomationEnrollment().rehydrate(
35
+ hydrate({
36
+ email: "ada@example.com",
37
+ status: "active",
38
+ enrolledAt: "2026-10-21T08:00:00.000Z",
39
+ nextSendAt: "2026-10-22T08:00:00.000Z",
40
+ attempts: 0,
41
+ }),
42
+ );
43
+ expect(m.enrolledAt).toBeInstanceOf(Date);
44
+ expect(m.enrolledAt.toISOString()).toBe("2026-10-21T08:00:00.000Z");
45
+ expect(m.nextSendAt).toBeInstanceOf(Date);
46
+ expect(m.nextSendAt?.toISOString()).toBe("2026-10-22T08:00:00.000Z");
47
+ expect(m.finishedAt).toBeUndefined();
48
+ });
49
+
50
+ it("reads the enrollment computed fields from attributes", () => {
51
+ const m = new EmailAutomationEnrollment().rehydrate(
52
+ hydrate({
53
+ email: "ada@example.com",
54
+ status: "active",
55
+ enrolledAt: "2026-10-21T08:00:00.000Z",
56
+ currentStepSubject: "Welcome aboard",
57
+ sentCount: 2,
58
+ }),
59
+ );
60
+ expect(m.currentStepSubject).toBe("Welcome aboard");
61
+ expect(m.sentCount).toBe(2);
62
+ });
63
+
64
+ it("omits undefined automation attributes and includes isActive when given", () => {
65
+ const body = new EmailAutomation().createJsonApi({ id: "a1", name: "Onboarding", isActive: true });
66
+ expect(body.data.type).toBe("email-automations");
67
+ expect(body.data.id).toBe("a1");
68
+ expect(body.data.attributes).toEqual({ name: "Onboarding", isActive: true });
69
+ expect("trigger" in body.data.attributes).toBe(false);
70
+ expect("exitTrigger" in body.data.attributes).toBe(false);
71
+ expect("replyTo" in body.data.attributes).toBe(false);
72
+ });
73
+
74
+ it("includes isActive false when given", () => {
75
+ const body = new EmailAutomation().createJsonApi({ id: "a1", name: "Onboarding", isActive: false });
76
+ expect(body.data.attributes.isActive).toBe(false);
77
+ });
78
+
79
+ it("writes the step's automation relationship", () => {
80
+ const body = new EmailAutomationStep().createJsonApi({
81
+ id: "s1",
82
+ automationId: "a1",
83
+ subject: "Hello",
84
+ body: "Hi {{name}}",
85
+ delayHours: 24,
86
+ });
87
+ expect(body.data.type).toBe("email-automation-steps");
88
+ expect(body.data.attributes).toEqual({ subject: "Hello", body: "Hi {{name}}", delayHours: 24 });
89
+ expect(body.data.relationships.automation).toEqual({ data: { type: "email-automations", id: "a1" } });
90
+ });
91
+
92
+ it("builds the next-step body", () => {
93
+ expect(new EmailAutomationStep().createNextJsonApi({ nextStepId: "s2" })).toEqual({
94
+ data: { type: "email-automation-steps", id: "s2" },
95
+ });
96
+ });
97
+
98
+ it("builds the unsubscribe body", () => {
99
+ expect(new EmailAutomationEnrollment().createUnsubscribeJsonApi({ scope: "all" })).toEqual({
100
+ data: { type: EMAIL_AUTOMATION_UNSUBSCRIBE_TYPE, attributes: { scope: "all" } },
101
+ });
102
+ });
103
+ });
@@ -0,0 +1,8 @@
1
+ export enum EmailAutomationEnrollmentFields {
2
+ email = "email",
3
+ name = "name",
4
+ status = "status",
5
+ currentStep = "currentStep",
6
+ nextSendAt = "nextSendAt",
7
+ sentCount = "sentCount",
8
+ }
@@ -0,0 +1,9 @@
1
+ export enum EmailAutomationFields {
2
+ name = "name",
3
+ trigger = "trigger",
4
+ status = "status",
5
+ steps = "steps",
6
+ active = "active",
7
+ completed = "completed",
8
+ unsubscribed = "unsubscribed",
9
+ }
@@ -0,0 +1,13 @@
1
+ export * from "./EmailAutomationInterface";
2
+ export * from "./EmailAutomation";
3
+ export * from "./EmailAutomationService";
4
+ export * from "./EmailAutomationStepInterface";
5
+ export * from "./EmailAutomationStep";
6
+ export * from "./EmailAutomationStepService";
7
+ export * from "./EmailAutomationEnrollmentInterface";
8
+ export * from "./EmailAutomationEnrollment";
9
+ export * from "./EmailAutomationEnrollmentService";
10
+ export * from "./EmailAutomationTriggerInterface";
11
+ export * from "./EmailAutomationTrigger";
12
+ export * from "./email-automation.fields";
13
+ export * from "./email-automation-enrollment.fields";
@@ -0,0 +1,2 @@
1
+ export * from "./useEmailAutomationTableStructure";
2
+ export * from "./useEmailAutomationEnrollmentTableStructure";
@@ -0,0 +1,126 @@
1
+ "use client";
2
+
3
+ import { ColumnDef } from "@tanstack/react-table";
4
+ import { useLocale, useTranslations } from "next-intl";
5
+ import { useMemo } from "react";
6
+ import { Badge } from "../../../shadcnui";
7
+ import { registerTableGenerator, TableContent, UseTableStructureHook } from "../../../hooks";
8
+ import { dateFormatLocale, formatDate } from "../../../utils/date-formatter";
9
+ import { EmailAutomationEnrollmentFields } from "../data/email-automation-enrollment.fields";
10
+ import { EmailAutomationEnrollmentInterface } from "../data/EmailAutomationEnrollmentInterface";
11
+
12
+ const STATUS_VARIANTS: Record<string, "softGreen" | "softBlue" | "softGray" | "softRed"> = {
13
+ active: "softGreen",
14
+ completed: "softBlue",
15
+ unsubscribed: "softGray",
16
+ exited: "softGray",
17
+ failed: "softRed",
18
+ };
19
+
20
+ export const useEmailAutomationEnrollmentTableStructure: UseTableStructureHook<
21
+ EmailAutomationEnrollmentInterface,
22
+ EmailAutomationEnrollmentFields
23
+ > = (params) => {
24
+ const t = useTranslations();
25
+ const locale = dateFormatLocale(useLocale());
26
+
27
+ const tableData = useMemo(() => {
28
+ return params.data.map((enrollment: EmailAutomationEnrollmentInterface) => {
29
+ const entry: TableContent<EmailAutomationEnrollmentInterface> = { jsonApiData: enrollment };
30
+ params.fields.forEach((field) => {
31
+ entry[field] = (enrollment as any)[field as keyof EmailAutomationEnrollmentInterface];
32
+ });
33
+ return entry;
34
+ });
35
+ }, [params.data, params.fields]);
36
+
37
+ const empty = <span className="text-muted-foreground text-xs">—</span>;
38
+
39
+ const fieldColumnMap: Partial<Record<EmailAutomationEnrollmentFields, () => any>> = {
40
+ [EmailAutomationEnrollmentFields.email]: () => ({
41
+ id: "email",
42
+ accessorKey: "email",
43
+ header: t("emailAutomations.recipients.columns.email"),
44
+ cell: ({ row }: { row: TableContent<EmailAutomationEnrollmentInterface> }) => {
45
+ const enrollment: EmailAutomationEnrollmentInterface = row.original.jsonApiData;
46
+ return <span className="text-sm">{enrollment.email}</span>;
47
+ },
48
+ enableSorting: false,
49
+ enableHiding: false,
50
+ }),
51
+ [EmailAutomationEnrollmentFields.name]: () => ({
52
+ id: "name",
53
+ accessorKey: "name",
54
+ header: t("emailAutomations.recipients.columns.name"),
55
+ cell: ({ row }: { row: TableContent<EmailAutomationEnrollmentInterface> }) => {
56
+ const enrollment: EmailAutomationEnrollmentInterface = row.original.jsonApiData;
57
+ return enrollment.name ? <span className="text-sm">{enrollment.name}</span> : empty;
58
+ },
59
+ enableSorting: false,
60
+ enableHiding: false,
61
+ }),
62
+ [EmailAutomationEnrollmentFields.status]: () => ({
63
+ id: "status",
64
+ accessorKey: "status",
65
+ header: t("emailAutomations.recipients.columns.status"),
66
+ cell: ({ row }: { row: TableContent<EmailAutomationEnrollmentInterface> }) => {
67
+ const enrollment: EmailAutomationEnrollmentInterface = row.original.jsonApiData;
68
+ return (
69
+ <Badge
70
+ variant={STATUS_VARIANTS[enrollment.status] ?? "softGray"}
71
+ title={enrollment.status === "failed" ? enrollment.lastError : undefined}
72
+ >
73
+ {t(`emailAutomations.recipients.status.${enrollment.status}`)}
74
+ </Badge>
75
+ );
76
+ },
77
+ enableSorting: false,
78
+ enableHiding: false,
79
+ }),
80
+ [EmailAutomationEnrollmentFields.currentStep]: () => ({
81
+ id: "currentStep",
82
+ accessorKey: "currentStep",
83
+ header: t("emailAutomations.recipients.columns.currentStep"),
84
+ cell: ({ row }: { row: TableContent<EmailAutomationEnrollmentInterface> }) => {
85
+ const enrollment: EmailAutomationEnrollmentInterface = row.original.jsonApiData;
86
+ return enrollment.currentStepSubject ? <span className="text-sm">{enrollment.currentStepSubject}</span> : empty;
87
+ },
88
+ enableSorting: false,
89
+ enableHiding: false,
90
+ }),
91
+ [EmailAutomationEnrollmentFields.nextSendAt]: () => ({
92
+ id: "nextSendAt",
93
+ accessorKey: "nextSendAt",
94
+ header: t("emailAutomations.recipients.columns.nextSendAt"),
95
+ cell: ({ row }: { row: TableContent<EmailAutomationEnrollmentInterface> }) => {
96
+ const enrollment: EmailAutomationEnrollmentInterface = row.original.jsonApiData;
97
+ if (!enrollment.nextSendAt) return empty;
98
+ return <span className="text-xs tabular-nums">{formatDate(enrollment.nextSendAt, "dateTime", locale)}</span>;
99
+ },
100
+ enableSorting: false,
101
+ enableHiding: false,
102
+ }),
103
+ [EmailAutomationEnrollmentFields.sentCount]: () => ({
104
+ id: "sentCount",
105
+ accessorKey: "sentCount",
106
+ header: t("emailAutomations.recipients.columns.sentCount"),
107
+ cell: ({ row }: { row: TableContent<EmailAutomationEnrollmentInterface> }) => {
108
+ const enrollment: EmailAutomationEnrollmentInterface = row.original.jsonApiData;
109
+ // Typography role 15 (numeric): tabular-nums, end-aligned.
110
+ return <span className="block text-xs tabular-nums text-end">{enrollment.sentCount ?? 0}</span>;
111
+ },
112
+ enableSorting: false,
113
+ enableHiding: false,
114
+ }),
115
+ };
116
+
117
+ const columns = useMemo(() => {
118
+ return params.fields.map((field) => fieldColumnMap[field]?.()).filter((col) => col !== undefined) as ColumnDef<
119
+ TableContent<EmailAutomationEnrollmentInterface>
120
+ >[];
121
+ }, [params.fields, fieldColumnMap, t, locale]);
122
+
123
+ return useMemo(() => ({ data: tableData, columns: columns }), [tableData, columns]);
124
+ };
125
+
126
+ registerTableGenerator("email-automation-enrollments", useEmailAutomationEnrollmentTableStructure);