@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
|
@@ -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,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,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,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);
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { ColumnDef } from "@tanstack/react-table";
|
|
4
|
+
import { useTranslations } from "next-intl";
|
|
5
|
+
import { useMemo } from "react";
|
|
6
|
+
import { Badge, Link } from "../../../shadcnui";
|
|
7
|
+
import { Modules } from "../../../core";
|
|
8
|
+
import { registerTableGenerator, TableContent, usePageUrlGenerator, UseTableStructureHook } from "../../../hooks";
|
|
9
|
+
import { EmailAutomationFields } from "../data/email-automation.fields";
|
|
10
|
+
import { EmailAutomationInterface } from "../data/EmailAutomationInterface";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* `context.triggerLabels` maps a trigger key to the label the API declares for
|
|
14
|
+
* it (EmailAutomationsList loads them). A key the app no longer declares falls
|
|
15
|
+
* back to the raw key, so a stale automation stays readable.
|
|
16
|
+
*/
|
|
17
|
+
export const useEmailAutomationTableStructure: UseTableStructureHook<
|
|
18
|
+
EmailAutomationInterface,
|
|
19
|
+
EmailAutomationFields
|
|
20
|
+
> = (params) => {
|
|
21
|
+
const t = useTranslations();
|
|
22
|
+
const generateUrl = usePageUrlGenerator();
|
|
23
|
+
const triggerLabels: Record<string, string> = params.context?.triggerLabels ?? {};
|
|
24
|
+
|
|
25
|
+
const tableData = useMemo(() => {
|
|
26
|
+
return params.data.map((automation: EmailAutomationInterface) => {
|
|
27
|
+
const entry: TableContent<EmailAutomationInterface> = { jsonApiData: automation };
|
|
28
|
+
params.fields.forEach((field) => {
|
|
29
|
+
entry[field] = (automation as any)[field as keyof EmailAutomationInterface];
|
|
30
|
+
});
|
|
31
|
+
return entry;
|
|
32
|
+
});
|
|
33
|
+
}, [params.data, params.fields]);
|
|
34
|
+
|
|
35
|
+
// Typography role 15 (numeric): tabular-nums, end-aligned. NEVER
|
|
36
|
+
// font-mono — no monospace font is loaded in these apps.
|
|
37
|
+
const numericColumn = (
|
|
38
|
+
field: EmailAutomationFields,
|
|
39
|
+
header: string,
|
|
40
|
+
read: (a: EmailAutomationInterface) => number,
|
|
41
|
+
) => ({
|
|
42
|
+
id: field,
|
|
43
|
+
accessorKey: field,
|
|
44
|
+
header: header,
|
|
45
|
+
cell: ({ row }: { row: TableContent<EmailAutomationInterface> }) => {
|
|
46
|
+
const automation: EmailAutomationInterface = row.original.jsonApiData;
|
|
47
|
+
return <span className="block text-xs tabular-nums text-end">{read(automation) ?? 0}</span>;
|
|
48
|
+
},
|
|
49
|
+
enableSorting: false,
|
|
50
|
+
enableHiding: false,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const fieldColumnMap: Partial<Record<EmailAutomationFields, () => any>> = {
|
|
54
|
+
[EmailAutomationFields.name]: () => ({
|
|
55
|
+
id: "name",
|
|
56
|
+
accessorKey: "name",
|
|
57
|
+
header: t("emailAutomations.list.columns.name"),
|
|
58
|
+
cell: ({ row }: { row: TableContent<EmailAutomationInterface> }) => {
|
|
59
|
+
const automation: EmailAutomationInterface = row.original.jsonApiData;
|
|
60
|
+
return <Link href={generateUrl({ page: Modules.EmailAutomation, id: automation.id })}>{automation.name}</Link>;
|
|
61
|
+
},
|
|
62
|
+
enableSorting: false,
|
|
63
|
+
enableHiding: false,
|
|
64
|
+
}),
|
|
65
|
+
[EmailAutomationFields.trigger]: () => ({
|
|
66
|
+
id: "trigger",
|
|
67
|
+
accessorKey: "trigger",
|
|
68
|
+
header: t("emailAutomations.list.columns.trigger"),
|
|
69
|
+
cell: ({ row }: { row: TableContent<EmailAutomationInterface> }) => {
|
|
70
|
+
const automation: EmailAutomationInterface = row.original.jsonApiData;
|
|
71
|
+
if (!automation.trigger) return <span className="text-muted-foreground text-xs">—</span>;
|
|
72
|
+
return <span className="text-sm">{triggerLabels[automation.trigger] ?? automation.trigger}</span>;
|
|
73
|
+
},
|
|
74
|
+
enableSorting: false,
|
|
75
|
+
enableHiding: false,
|
|
76
|
+
}),
|
|
77
|
+
[EmailAutomationFields.status]: () => ({
|
|
78
|
+
id: "status",
|
|
79
|
+
accessorKey: "status",
|
|
80
|
+
header: t("emailAutomations.list.columns.status"),
|
|
81
|
+
cell: ({ row }: { row: TableContent<EmailAutomationInterface> }) => {
|
|
82
|
+
const automation: EmailAutomationInterface = row.original.jsonApiData;
|
|
83
|
+
return automation.isActive ? (
|
|
84
|
+
<Badge variant="softGreen">{t("emailAutomations.list.status.on")}</Badge>
|
|
85
|
+
) : (
|
|
86
|
+
<Badge variant="softGray">{t("emailAutomations.list.status.off")}</Badge>
|
|
87
|
+
);
|
|
88
|
+
},
|
|
89
|
+
enableSorting: false,
|
|
90
|
+
enableHiding: false,
|
|
91
|
+
}),
|
|
92
|
+
[EmailAutomationFields.steps]: () =>
|
|
93
|
+
numericColumn(EmailAutomationFields.steps, t("emailAutomations.list.columns.steps"), (a) => a.stepCount),
|
|
94
|
+
[EmailAutomationFields.active]: () =>
|
|
95
|
+
numericColumn(EmailAutomationFields.active, t("emailAutomations.list.columns.active"), (a) => a.activeCount),
|
|
96
|
+
[EmailAutomationFields.completed]: () =>
|
|
97
|
+
numericColumn(
|
|
98
|
+
EmailAutomationFields.completed,
|
|
99
|
+
t("emailAutomations.list.columns.completed"),
|
|
100
|
+
(a) => a.completedCount,
|
|
101
|
+
),
|
|
102
|
+
[EmailAutomationFields.unsubscribed]: () =>
|
|
103
|
+
numericColumn(
|
|
104
|
+
EmailAutomationFields.unsubscribed,
|
|
105
|
+
t("emailAutomations.list.columns.unsubscribed"),
|
|
106
|
+
(a) => a.unsubscribedCount,
|
|
107
|
+
),
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const columns = useMemo(() => {
|
|
111
|
+
return params.fields.map((field) => fieldColumnMap[field]?.()).filter((col) => col !== undefined) as ColumnDef<
|
|
112
|
+
TableContent<EmailAutomationInterface>
|
|
113
|
+
>[];
|
|
114
|
+
}, [params.fields, fieldColumnMap, t, generateUrl]);
|
|
115
|
+
|
|
116
|
+
return useMemo(() => ({ data: tableData, columns: columns }), [tableData, columns]);
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
registerTableGenerator("email-automations", useEmailAutomationTableStructure);
|