@carlonicora/nextjs-jsonapi 6.7.0 → 6.9.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/{AssistantMessageInterface-0ZQ1EJKO.d.mts → AssistantMessageInterface-BE3QxI54.d.mts} +1 -1
- package/dist/{AssistantMessageInterface-CA-hZ5Wj.d.ts → AssistantMessageInterface-nXpD_d-m.d.ts} +1 -1
- package/dist/{BlockNoteEditor-HWJBQAKG.mjs → BlockNoteEditor-WN73IHXR.mjs} +4 -4
- package/dist/{BlockNoteEditor-BSFR2GPI.js → BlockNoteEditor-XXH3VBZ7.js} +19 -19
- package/dist/{BlockNoteEditor-BSFR2GPI.js.map → BlockNoteEditor-XXH3VBZ7.js.map} +1 -1
- package/dist/{BlockNoteViewer-NHS6CCMS.js → BlockNoteViewer-EKAWVLIR.js} +3 -3
- package/dist/{BlockNoteViewer-NHS6CCMS.js.map → BlockNoteViewer-EKAWVLIR.js.map} +1 -1
- package/dist/{BlockNoteViewer-IIBUU7J7.mjs → BlockNoteViewer-RWRI3QO2.mjs} +2 -2
- package/dist/{BlogPostInterface-CTGfsR1I.d.ts → BlogPostInterface-2sVqvXFd.d.ts} +1 -1
- package/dist/{BlogPostInterface-BN2mXc46.d.mts → BlogPostInterface-BgJLFpaa.d.mts} +1 -1
- package/dist/{HandbookSectionInterface-BYsmnPEz.d.mts → HandbookSectionInterface-BPgqqwgw.d.mts} +20 -1
- package/dist/{HandbookSectionInterface-OePKPbqN.d.ts → HandbookSectionInterface-_51oA3om.d.ts} +20 -1
- package/dist/{auth.interface-DuX87BZ3.d.mts → auth.interface-BAYLbYjs.d.mts} +1 -1
- package/dist/{auth.interface-B4tRE0k0.d.ts → auth.interface-BjYwwfy9.d.ts} +1 -1
- package/dist/billing/index.js +492 -492
- package/dist/billing/index.mjs +3 -3
- package/dist/{chunk-EC4PQ44E.mjs → chunk-3TNGKMJU.mjs} +2 -2
- package/dist/{chunk-QNIWATQD.mjs → chunk-6HFRXEF5.mjs} +331 -3
- package/dist/chunk-6HFRXEF5.mjs.map +1 -0
- package/dist/{chunk-OYRJ32KR.mjs → chunk-AWLTSKMM.mjs} +2 -2
- package/dist/{chunk-XYKDBOCE.mjs → chunk-DPAM7BCE.mjs} +2 -2
- package/dist/{chunk-PYDDFFQM.js → chunk-G3YKDIAL.js} +3 -3
- package/dist/{chunk-PYDDFFQM.js.map → chunk-G3YKDIAL.js.map} +1 -1
- package/dist/{chunk-3HOJNAZB.js → chunk-KRQRJA4J.js} +8 -8
- package/dist/{chunk-3HOJNAZB.js.map → chunk-KRQRJA4J.js.map} +1 -1
- package/dist/{chunk-S4NKWMX5.mjs → chunk-Q4XLE3QO.mjs} +1738 -1308
- package/dist/chunk-Q4XLE3QO.mjs.map +1 -0
- package/dist/{chunk-73XWQA6U.js → chunk-RNZ3IK5J.js} +4 -4
- package/dist/{chunk-73XWQA6U.js.map → chunk-RNZ3IK5J.js.map} +1 -1
- package/dist/{chunk-6KE7UBYY.js → chunk-SQTXJPRE.js} +505 -177
- package/dist/chunk-SQTXJPRE.js.map +1 -0
- package/dist/{chunk-PCCUPEBU.js → chunk-ZCM4JLM7.js} +1381 -951
- package/dist/chunk-ZCM4JLM7.js.map +1 -0
- package/dist/client/index.d.mts +3 -3
- package/dist/client/index.d.ts +3 -3
- package/dist/client/index.js +4 -4
- package/dist/client/index.mjs +3 -3
- package/dist/components/index.d.mts +62 -8
- package/dist/components/index.d.ts +62 -8
- package/dist/components/index.js +14 -4
- package/dist/components/index.js.map +1 -1
- package/dist/components/index.mjs +13 -3
- package/dist/contexts/index.d.mts +4 -4
- package/dist/contexts/index.d.ts +4 -4
- package/dist/contexts/index.js +4 -4
- package/dist/contexts/index.mjs +3 -3
- package/dist/core/index.d.mts +146 -13
- package/dist/core/index.d.ts +146 -13
- package/dist/core/index.js +24 -2
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +23 -1
- package/dist/email-automations/index.d.mts +6 -1
- package/dist/email-automations/index.d.ts +6 -1
- package/dist/email-automations/index.js +101 -94
- package/dist/email-automations/index.js.map +1 -1
- package/dist/email-automations/index.mjs +12 -5
- package/dist/email-automations/index.mjs.map +1 -1
- package/dist/features/analytics/index.d.mts +1 -1
- package/dist/features/analytics/index.d.ts +1 -1
- package/dist/features/analytics/index.js +121 -121
- 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/blog/index.d.mts +3 -3
- package/dist/features/blog/index.d.ts +3 -3
- package/dist/features/blog/index.js +150 -150
- package/dist/features/blog/index.mjs +3 -3
- package/dist/features/blog/server.d.mts +3 -3
- package/dist/features/blog/server.d.ts +3 -3
- package/dist/features/blog/server.js +16 -16
- package/dist/features/blog/server.mjs +1 -1
- package/dist/features/help/index.js +37 -37
- package/dist/features/help/index.mjs +3 -3
- package/dist/features/tokenusage/index.js +127 -127
- package/dist/features/tokenusage/index.mjs +4 -4
- package/dist/index.d.mts +8 -8
- package/dist/index.d.ts +8 -8
- package/dist/index.js +25 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +24 -2
- package/dist/{notification.interface-DQReXAD7.d.ts → notification.interface-DQItyY_Q.d.ts} +1 -1
- package/dist/{notification.interface-CF5h90U2.d.mts → notification.interface-DoVS5RZB.d.mts} +1 -1
- package/dist/{s3.service-CRGJbO-6.d.mts → s3.service-BG136Btz.d.mts} +3 -3
- package/dist/{s3.service-DZG-VyLY.d.ts → s3.service-CsrsRPRn.d.ts} +3 -3
- package/dist/server/index.d.mts +4 -4
- package/dist/server/index.d.ts +4 -4
- package/dist/server/index.js +3 -3
- package/dist/server/index.mjs +2 -2
- package/dist/{useSocket-CwSoq6VS.d.mts → useSocket-C8fqU4od.d.mts} +1 -1
- package/dist/{useSocket-BM3q9A0C.d.ts → useSocket-IRHtk2xc.d.ts} +1 -1
- package/dist/{user.interface-BYnGJ7ss.d.mts → user.interface-Bm-0TbsB.d.mts} +6 -0
- package/dist/{user.interface-BqPjVyJI.d.ts → user.interface-CKBIEvNu.d.ts} +6 -0
- package/messages/en.json +62 -0
- package/messages/it.json +62 -0
- package/package.json +1 -1
- package/src/components/index.ts +1 -0
- package/src/core/index.ts +3 -0
- package/src/core/registry/ModuleRegistry.ts +4 -0
- package/src/features/company/data/company.interface.ts +7 -0
- package/src/features/company/data/company.spec.ts +23 -0
- package/src/features/company/data/company.ts +6 -0
- package/src/features/company/hooks/useSubscriptionStatus.spec.ts +59 -0
- package/src/features/company/hooks/useSubscriptionStatus.ts +5 -3
- package/src/features/email-automation/components/unsubscribe/EmailUnsubscribe.tsx +14 -2
- package/src/features/email-blast/EmailBlastAudienceModule.ts +12 -0
- package/src/features/email-blast/EmailBlastDeliveryModule.ts +12 -0
- package/src/features/email-blast/EmailBlastModule.ts +28 -0
- package/src/features/email-blast/components/containers/EmailBlastContainer.tsx +145 -0
- package/src/features/email-blast/components/containers/EmailBlastsListContainer.tsx +36 -0
- package/src/features/email-blast/components/forms/EmailBlastEditor.tsx +132 -0
- package/src/features/email-blast/components/forms/EmailBlastSendDialog.tsx +104 -0
- package/src/features/email-blast/components/index.ts +5 -0
- package/src/features/email-blast/components/lists/EmailBlastsList.tsx +57 -0
- package/src/features/email-blast/data/EmailBlast.ts +89 -0
- package/src/features/email-blast/data/EmailBlastAudience.ts +22 -0
- package/src/features/email-blast/data/EmailBlastAudienceInterface.ts +5 -0
- package/src/features/email-blast/data/EmailBlastDelivery.ts +36 -0
- package/src/features/email-blast/data/EmailBlastDeliveryInterface.ts +7 -0
- package/src/features/email-blast/data/EmailBlastDeliveryService.ts +25 -0
- package/src/features/email-blast/data/EmailBlastInterface.ts +22 -0
- package/src/features/email-blast/data/EmailBlastService.ts +101 -0
- package/src/features/email-blast/data/__tests__/EmailBlast.spec.ts +73 -0
- package/src/features/email-blast/data/email-blast.fields.ts +8 -0
- package/src/features/email-blast/data/email-blast.segments.ts +10 -0
- package/src/features/email-blast/data/index.ts +10 -0
- package/src/features/email-blast/hooks/useEmailBlastTableStructure.tsx +104 -0
- package/src/features/email-blast/index.ts +4 -0
- package/src/features/index.ts +1 -0
- package/dist/chunk-6KE7UBYY.js.map +0 -1
- package/dist/chunk-PCCUPEBU.js.map +0 -1
- package/dist/chunk-QNIWATQD.mjs.map +0 -1
- package/dist/chunk-S4NKWMX5.mjs.map +0 -1
- /package/dist/{BlockNoteEditor-HWJBQAKG.mjs.map → BlockNoteEditor-WN73IHXR.mjs.map} +0 -0
- /package/dist/{BlockNoteViewer-IIBUU7J7.mjs.map → BlockNoteViewer-RWRI3QO2.mjs.map} +0 -0
- /package/dist/{chunk-EC4PQ44E.mjs.map → chunk-3TNGKMJU.mjs.map} +0 -0
- /package/dist/{chunk-OYRJ32KR.mjs.map → chunk-AWLTSKMM.mjs.map} +0 -0
- /package/dist/{chunk-XYKDBOCE.mjs.map → chunk-DPAM7BCE.mjs.map} +0 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { beforeAll, describe, expect, it } from "vitest";
|
|
2
|
+
import { ModuleRegistry } from "../../../../core/registry/ModuleRegistry";
|
|
3
|
+
import { ModuleFactory, ModuleWithPermissions } from "../../../../permissions/types";
|
|
4
|
+
import { EmailBlastAudienceModule } from "../../EmailBlastAudienceModule";
|
|
5
|
+
import { EmailBlastDeliveryModule } from "../../EmailBlastDeliveryModule";
|
|
6
|
+
import { EmailBlastModule } from "../../EmailBlastModule";
|
|
7
|
+
import { EmailBlast } from "../EmailBlast";
|
|
8
|
+
import { EmailBlastAudience } from "../EmailBlastAudience";
|
|
9
|
+
|
|
10
|
+
// createJsonApi reads Modules.X.name (resolved lazily via ModuleRegistry), so
|
|
11
|
+
// the modules must be registered before a model serialises — same setup as
|
|
12
|
+
// email-automation-models.spec.
|
|
13
|
+
const moduleFactory: ModuleFactory = (params) =>
|
|
14
|
+
({
|
|
15
|
+
pageUrl: params.pageUrl,
|
|
16
|
+
name: params.name,
|
|
17
|
+
model: params.model,
|
|
18
|
+
}) as ModuleWithPermissions;
|
|
19
|
+
|
|
20
|
+
beforeAll(() => {
|
|
21
|
+
ModuleRegistry.register("EmailBlast", EmailBlastModule(moduleFactory));
|
|
22
|
+
ModuleRegistry.register("EmailBlastDelivery", EmailBlastDeliveryModule(moduleFactory));
|
|
23
|
+
ModuleRegistry.register("EmailBlastAudience", EmailBlastAudienceModule(moduleFactory));
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const hydrate = (attributes: Record<string, unknown>) =>
|
|
27
|
+
({ jsonApi: { id: "x", type: "t", attributes }, included: [] }) as any;
|
|
28
|
+
|
|
29
|
+
describe("email blast models", () => {
|
|
30
|
+
it("rehydrate parses sentAt to a Date and defaults counters to 0", () => {
|
|
31
|
+
const sent = new EmailBlast().rehydrate(
|
|
32
|
+
hydrate({
|
|
33
|
+
subject: "News",
|
|
34
|
+
body: "Hello",
|
|
35
|
+
status: "sent",
|
|
36
|
+
sentAt: "2026-10-27T08:00:00.000Z",
|
|
37
|
+
recipientCount: 12,
|
|
38
|
+
}),
|
|
39
|
+
);
|
|
40
|
+
expect(sent.sentAt).toBeInstanceOf(Date);
|
|
41
|
+
expect(sent.sentAt?.toISOString()).toBe("2026-10-27T08:00:00.000Z");
|
|
42
|
+
expect(sent.recipientCount).toBe(12);
|
|
43
|
+
expect(sent.status).toBe("sent");
|
|
44
|
+
expect(sent.sentCount).toBe(0);
|
|
45
|
+
expect(sent.failedCount).toBe(0);
|
|
46
|
+
expect(sent.unsubscribedCount).toBe(0);
|
|
47
|
+
|
|
48
|
+
const draft = new EmailBlast().rehydrate(hydrate({ subject: "News", body: "Hello" }));
|
|
49
|
+
expect(draft.sentAt).toBeUndefined();
|
|
50
|
+
expect(draft.recipientCount).toBeUndefined();
|
|
51
|
+
expect(draft.replyTo).toBeUndefined();
|
|
52
|
+
expect(draft.status).toBe("draft");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("createJsonApi emits only subject, body and replyTo", () => {
|
|
56
|
+
const body = new EmailBlast().createJsonApi({
|
|
57
|
+
id: "b1",
|
|
58
|
+
subject: "News",
|
|
59
|
+
body: "Hello",
|
|
60
|
+
replyTo: "gm@example.com",
|
|
61
|
+
...({ status: "sent", sentCount: 3, failedCount: 1, unsubscribedCount: 1, sentAt: new Date() } as object),
|
|
62
|
+
});
|
|
63
|
+
expect(body.data.type).toBe("email-blasts");
|
|
64
|
+
expect(body.data.id).toBe("b1");
|
|
65
|
+
expect(body.data.attributes).toEqual({ subject: "News", body: "Hello", replyTo: "gm@example.com" });
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("EmailBlastAudience rehydrates recipientCount", () => {
|
|
69
|
+
const audience = new EmailBlastAudience().rehydrate(hydrate({ recipientCount: 42 }));
|
|
70
|
+
expect(audience.recipientCount).toBe(42);
|
|
71
|
+
expect(new EmailBlastAudience().rehydrate(hydrate({})).recipientCount).toBe(0);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* URL segments of the email-blast child routes. The only place these strings
|
|
3
|
+
* are written: services build every child URL from here through EndpointCreator.
|
|
4
|
+
*/
|
|
5
|
+
export const EMAIL_BLAST_SEGMENTS = {
|
|
6
|
+
audience: "recipient-count",
|
|
7
|
+
test: "test",
|
|
8
|
+
send: "send",
|
|
9
|
+
unsubscribe: "unsubscribe",
|
|
10
|
+
} as const;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export * from "./email-blast.segments";
|
|
2
|
+
export * from "./EmailBlastInterface";
|
|
3
|
+
export * from "./EmailBlast";
|
|
4
|
+
export * from "./EmailBlastService";
|
|
5
|
+
export * from "./EmailBlastDeliveryInterface";
|
|
6
|
+
export * from "./EmailBlastDelivery";
|
|
7
|
+
export * from "./EmailBlastDeliveryService";
|
|
8
|
+
export * from "./EmailBlastAudienceInterface";
|
|
9
|
+
export * from "./EmailBlastAudience";
|
|
10
|
+
export * from "./email-blast.fields";
|
|
@@ -0,0 +1,104 @@
|
|
|
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 { useI18nLocale } from "../../../i18n";
|
|
10
|
+
import { EmailBlastFields } from "../data/email-blast.fields";
|
|
11
|
+
import { EmailBlastInterface, EmailBlastStatus } from "../data/EmailBlastInterface";
|
|
12
|
+
|
|
13
|
+
const STATUS_VARIANTS: Record<EmailBlastStatus, "softGray" | "softAmber" | "softGreen"> = {
|
|
14
|
+
draft: "softGray",
|
|
15
|
+
sending: "softAmber",
|
|
16
|
+
sent: "softGreen",
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const useEmailBlastTableStructure: UseTableStructureHook<EmailBlastInterface, EmailBlastFields> = (params) => {
|
|
20
|
+
const t = useTranslations();
|
|
21
|
+
const locale = useI18nLocale();
|
|
22
|
+
const generateUrl = usePageUrlGenerator();
|
|
23
|
+
|
|
24
|
+
const tableData = useMemo(() => {
|
|
25
|
+
return params.data.map((blast: EmailBlastInterface) => {
|
|
26
|
+
const entry: TableContent<EmailBlastInterface> = { jsonApiData: blast };
|
|
27
|
+
params.fields.forEach((field) => {
|
|
28
|
+
entry[field] = (blast as any)[field as keyof EmailBlastInterface];
|
|
29
|
+
});
|
|
30
|
+
return entry;
|
|
31
|
+
});
|
|
32
|
+
}, [params.data, params.fields]);
|
|
33
|
+
|
|
34
|
+
// Typography role 15 (numeric): tabular-nums, end-aligned. NEVER
|
|
35
|
+
// font-mono — no monospace font is loaded in these apps.
|
|
36
|
+
const numericColumn = (field: EmailBlastFields, header: string, read: (b: EmailBlastInterface) => number) => ({
|
|
37
|
+
id: field,
|
|
38
|
+
accessorKey: field,
|
|
39
|
+
header: header,
|
|
40
|
+
cell: ({ row }: { row: TableContent<EmailBlastInterface> }) => {
|
|
41
|
+
const blast: EmailBlastInterface = row.original.jsonApiData;
|
|
42
|
+
return <span className="block text-xs tabular-nums text-end">{read(blast) ?? 0}</span>;
|
|
43
|
+
},
|
|
44
|
+
enableSorting: false,
|
|
45
|
+
enableHiding: false,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const fieldColumnMap: Partial<Record<EmailBlastFields, () => any>> = {
|
|
49
|
+
[EmailBlastFields.subject]: () => ({
|
|
50
|
+
id: "subject",
|
|
51
|
+
accessorKey: "subject",
|
|
52
|
+
header: t("emailBlasts.list.columns.subject"),
|
|
53
|
+
cell: ({ row }: { row: TableContent<EmailBlastInterface> }) => {
|
|
54
|
+
const blast: EmailBlastInterface = row.original.jsonApiData;
|
|
55
|
+
return <Link href={generateUrl({ page: Modules.EmailBlast, id: blast.id })}>{blast.subject}</Link>;
|
|
56
|
+
},
|
|
57
|
+
enableSorting: false,
|
|
58
|
+
enableHiding: false,
|
|
59
|
+
}),
|
|
60
|
+
[EmailBlastFields.status]: () => ({
|
|
61
|
+
id: "status",
|
|
62
|
+
accessorKey: "status",
|
|
63
|
+
header: t("emailBlasts.list.columns.status"),
|
|
64
|
+
cell: ({ row }: { row: TableContent<EmailBlastInterface> }) => {
|
|
65
|
+
const blast: EmailBlastInterface = row.original.jsonApiData;
|
|
66
|
+
return <Badge variant={STATUS_VARIANTS[blast.status]}>{t(`emailBlasts.list.status.${blast.status}`)}</Badge>;
|
|
67
|
+
},
|
|
68
|
+
enableSorting: false,
|
|
69
|
+
enableHiding: false,
|
|
70
|
+
}),
|
|
71
|
+
[EmailBlastFields.sentAt]: () => ({
|
|
72
|
+
id: "sentAt",
|
|
73
|
+
accessorKey: "sentAt",
|
|
74
|
+
header: t("emailBlasts.list.columns.sentAt"),
|
|
75
|
+
cell: ({ row }: { row: TableContent<EmailBlastInterface> }) => {
|
|
76
|
+
const blast: EmailBlastInterface = row.original.jsonApiData;
|
|
77
|
+
if (!blast.sentAt) return <span className="text-muted-foreground text-xs">—</span>;
|
|
78
|
+
return <span className="text-sm">{blast.sentAt.toLocaleDateString(locale)}</span>;
|
|
79
|
+
},
|
|
80
|
+
enableSorting: false,
|
|
81
|
+
enableHiding: false,
|
|
82
|
+
}),
|
|
83
|
+
[EmailBlastFields.sent]: () =>
|
|
84
|
+
numericColumn(EmailBlastFields.sent, t("emailBlasts.list.columns.sent"), (b) => b.sentCount),
|
|
85
|
+
[EmailBlastFields.failed]: () =>
|
|
86
|
+
numericColumn(EmailBlastFields.failed, t("emailBlasts.list.columns.failed"), (b) => b.failedCount),
|
|
87
|
+
[EmailBlastFields.unsubscribed]: () =>
|
|
88
|
+
numericColumn(
|
|
89
|
+
EmailBlastFields.unsubscribed,
|
|
90
|
+
t("emailBlasts.list.columns.unsubscribed"),
|
|
91
|
+
(b) => b.unsubscribedCount,
|
|
92
|
+
),
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const columns = useMemo(() => {
|
|
96
|
+
return params.fields.map((field) => fieldColumnMap[field]?.()).filter((col) => col !== undefined) as ColumnDef<
|
|
97
|
+
TableContent<EmailBlastInterface>
|
|
98
|
+
>[];
|
|
99
|
+
}, [params.fields, fieldColumnMap, t, generateUrl, locale]);
|
|
100
|
+
|
|
101
|
+
return useMemo(() => ({ data: tableData, columns: columns }), [tableData, columns]);
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
registerTableGenerator("email-blasts", useEmailBlastTableStructure);
|
package/src/features/index.ts
CHANGED
|
@@ -12,6 +12,7 @@ export * from "./billing/stripe-usage";
|
|
|
12
12
|
export * from "./billing/stripe-promotion-code";
|
|
13
13
|
export * from "./company";
|
|
14
14
|
export * from "./content";
|
|
15
|
+
export * from "./email-blast";
|
|
15
16
|
export * from "./feature";
|
|
16
17
|
export * from "./module";
|
|
17
18
|
export * from "./notification";
|