@carlonicora/nextjs-jsonapi 6.8.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.
Files changed (106) hide show
  1. package/dist/{BlockNoteEditor-D3LDTJCP.mjs → BlockNoteEditor-WN73IHXR.mjs} +4 -4
  2. package/dist/{BlockNoteEditor-FB6FQFRF.js → BlockNoteEditor-XXH3VBZ7.js} +19 -19
  3. package/dist/{BlockNoteEditor-FB6FQFRF.js.map → BlockNoteEditor-XXH3VBZ7.js.map} +1 -1
  4. package/dist/{BlockNoteViewer-LGFMGTYX.js → BlockNoteViewer-EKAWVLIR.js} +3 -3
  5. package/dist/{BlockNoteViewer-LGFMGTYX.js.map → BlockNoteViewer-EKAWVLIR.js.map} +1 -1
  6. package/dist/{BlockNoteViewer-YSPYUDZE.mjs → BlockNoteViewer-RWRI3QO2.mjs} +2 -2
  7. package/dist/{HandbookSectionInterface-BYsmnPEz.d.mts → HandbookSectionInterface-BPgqqwgw.d.mts} +20 -1
  8. package/dist/{HandbookSectionInterface-OePKPbqN.d.ts → HandbookSectionInterface-_51oA3om.d.ts} +20 -1
  9. package/dist/billing/index.js +492 -492
  10. package/dist/billing/index.mjs +3 -3
  11. package/dist/{chunk-D44UDP4G.mjs → chunk-3TNGKMJU.mjs} +2 -2
  12. package/dist/{chunk-VT5HRZH7.mjs → chunk-6HFRXEF5.mjs} +326 -3
  13. package/dist/chunk-6HFRXEF5.mjs.map +1 -0
  14. package/dist/{chunk-5HBFQEYU.mjs → chunk-AWLTSKMM.mjs} +2 -2
  15. package/dist/{chunk-TL5O7EGW.mjs → chunk-DPAM7BCE.mjs} +2 -2
  16. package/dist/{chunk-PSLT5JLE.js → chunk-G3YKDIAL.js} +3 -3
  17. package/dist/{chunk-PSLT5JLE.js.map → chunk-G3YKDIAL.js.map} +1 -1
  18. package/dist/{chunk-ZXK2OMCC.js → chunk-KRQRJA4J.js} +8 -8
  19. package/dist/{chunk-ZXK2OMCC.js.map → chunk-KRQRJA4J.js.map} +1 -1
  20. package/dist/{chunk-5HTX4RDA.mjs → chunk-Q4XLE3QO.mjs} +1737 -1307
  21. package/dist/chunk-Q4XLE3QO.mjs.map +1 -0
  22. package/dist/{chunk-JHT32ODN.js → chunk-RNZ3IK5J.js} +4 -4
  23. package/dist/{chunk-JHT32ODN.js.map → chunk-RNZ3IK5J.js.map} +1 -1
  24. package/dist/{chunk-UZP7KRAV.js → chunk-SQTXJPRE.js} +500 -177
  25. package/dist/chunk-SQTXJPRE.js.map +1 -0
  26. package/dist/{chunk-UT4GVYJE.js → chunk-ZCM4JLM7.js} +1380 -950
  27. package/dist/chunk-ZCM4JLM7.js.map +1 -0
  28. package/dist/client/index.js +4 -4
  29. package/dist/client/index.mjs +3 -3
  30. package/dist/components/index.d.mts +57 -3
  31. package/dist/components/index.d.ts +57 -3
  32. package/dist/components/index.js +14 -4
  33. package/dist/components/index.js.map +1 -1
  34. package/dist/components/index.mjs +13 -3
  35. package/dist/contexts/index.js +4 -4
  36. package/dist/contexts/index.mjs +3 -3
  37. package/dist/core/index.d.mts +134 -3
  38. package/dist/core/index.d.ts +134 -3
  39. package/dist/core/index.js +24 -2
  40. package/dist/core/index.js.map +1 -1
  41. package/dist/core/index.mjs +23 -1
  42. package/dist/email-automations/index.d.mts +6 -1
  43. package/dist/email-automations/index.d.ts +6 -1
  44. package/dist/email-automations/index.js +101 -94
  45. package/dist/email-automations/index.js.map +1 -1
  46. package/dist/email-automations/index.mjs +12 -5
  47. package/dist/email-automations/index.mjs.map +1 -1
  48. package/dist/features/analytics/index.js +121 -121
  49. package/dist/features/analytics/index.mjs +4 -4
  50. package/dist/features/analytics/server.js +4 -4
  51. package/dist/features/analytics/server.mjs +2 -2
  52. package/dist/features/blog/index.js +150 -150
  53. package/dist/features/blog/index.mjs +3 -3
  54. package/dist/features/blog/server.js +16 -16
  55. package/dist/features/blog/server.mjs +1 -1
  56. package/dist/features/help/index.js +37 -37
  57. package/dist/features/help/index.mjs +3 -3
  58. package/dist/features/tokenusage/index.js +127 -127
  59. package/dist/features/tokenusage/index.mjs +4 -4
  60. package/dist/index.d.mts +2 -2
  61. package/dist/index.d.ts +2 -2
  62. package/dist/index.js +25 -3
  63. package/dist/index.js.map +1 -1
  64. package/dist/index.mjs +24 -2
  65. package/dist/server/index.js +3 -3
  66. package/dist/server/index.mjs +2 -2
  67. package/messages/en.json +62 -0
  68. package/messages/it.json +62 -0
  69. package/package.json +1 -1
  70. package/src/components/index.ts +1 -0
  71. package/src/core/index.ts +3 -0
  72. package/src/core/registry/ModuleRegistry.ts +4 -0
  73. package/src/features/email-automation/components/unsubscribe/EmailUnsubscribe.tsx +14 -2
  74. package/src/features/email-blast/EmailBlastAudienceModule.ts +12 -0
  75. package/src/features/email-blast/EmailBlastDeliveryModule.ts +12 -0
  76. package/src/features/email-blast/EmailBlastModule.ts +28 -0
  77. package/src/features/email-blast/components/containers/EmailBlastContainer.tsx +145 -0
  78. package/src/features/email-blast/components/containers/EmailBlastsListContainer.tsx +36 -0
  79. package/src/features/email-blast/components/forms/EmailBlastEditor.tsx +132 -0
  80. package/src/features/email-blast/components/forms/EmailBlastSendDialog.tsx +104 -0
  81. package/src/features/email-blast/components/index.ts +5 -0
  82. package/src/features/email-blast/components/lists/EmailBlastsList.tsx +57 -0
  83. package/src/features/email-blast/data/EmailBlast.ts +89 -0
  84. package/src/features/email-blast/data/EmailBlastAudience.ts +22 -0
  85. package/src/features/email-blast/data/EmailBlastAudienceInterface.ts +5 -0
  86. package/src/features/email-blast/data/EmailBlastDelivery.ts +36 -0
  87. package/src/features/email-blast/data/EmailBlastDeliveryInterface.ts +7 -0
  88. package/src/features/email-blast/data/EmailBlastDeliveryService.ts +25 -0
  89. package/src/features/email-blast/data/EmailBlastInterface.ts +22 -0
  90. package/src/features/email-blast/data/EmailBlastService.ts +101 -0
  91. package/src/features/email-blast/data/__tests__/EmailBlast.spec.ts +73 -0
  92. package/src/features/email-blast/data/email-blast.fields.ts +8 -0
  93. package/src/features/email-blast/data/email-blast.segments.ts +10 -0
  94. package/src/features/email-blast/data/index.ts +10 -0
  95. package/src/features/email-blast/hooks/useEmailBlastTableStructure.tsx +104 -0
  96. package/src/features/email-blast/index.ts +4 -0
  97. package/src/features/index.ts +1 -0
  98. package/dist/chunk-5HTX4RDA.mjs.map +0 -1
  99. package/dist/chunk-UT4GVYJE.js.map +0 -1
  100. package/dist/chunk-UZP7KRAV.js.map +0 -1
  101. package/dist/chunk-VT5HRZH7.mjs.map +0 -1
  102. /package/dist/{BlockNoteEditor-D3LDTJCP.mjs.map → BlockNoteEditor-WN73IHXR.mjs.map} +0 -0
  103. /package/dist/{BlockNoteViewer-YSPYUDZE.mjs.map → BlockNoteViewer-RWRI3QO2.mjs.map} +0 -0
  104. /package/dist/{chunk-D44UDP4G.mjs.map → chunk-3TNGKMJU.mjs.map} +0 -0
  105. /package/dist/{chunk-5HBFQEYU.mjs.map → chunk-AWLTSKMM.mjs.map} +0 -0
  106. /package/dist/{chunk-TL5O7EGW.mjs.map → chunk-DPAM7BCE.mjs.map} +0 -0
@@ -0,0 +1,7 @@
1
+ import { ApiDataInterface } from "../../../core";
2
+
3
+ export type EmailBlastDeliveryStatus = "pending" | "sending" | "sent" | "skipped" | "failed";
4
+
5
+ export interface EmailBlastDeliveryInterface extends ApiDataInterface {
6
+ get status(): EmailBlastDeliveryStatus;
7
+ }
@@ -0,0 +1,25 @@
1
+ import { AbstractService, EndpointCreator, HttpMethod, Modules } from "../../../core";
2
+ import { EMAIL_BLAST_SEGMENTS } from "./email-blast.segments";
3
+ import { EmailBlastDelivery } from "./EmailBlastDelivery";
4
+
5
+ export class EmailBlastDeliveryService extends AbstractService {
6
+ /**
7
+ * POST email-blasts/unsubscribe/:token (public). Replies 204: the token holder
8
+ * receives nothing back.
9
+ */
10
+ static async unsubscribe(params: { token: string }): Promise<void> {
11
+ const delivery = new EmailBlastDelivery();
12
+
13
+ await this.callApi({
14
+ type: Modules.EmailBlastDelivery,
15
+ method: HttpMethod.POST,
16
+ endpoint: new EndpointCreator({
17
+ endpoint: Modules.EmailBlast,
18
+ id: EMAIL_BLAST_SEGMENTS.unsubscribe,
19
+ childEndpoint: params.token,
20
+ }).generate(),
21
+ input: delivery.createUnsubscribeJsonApi(),
22
+ overridesJsonApiCreation: true,
23
+ });
24
+ }
25
+ }
@@ -0,0 +1,22 @@
1
+ import { ApiDataInterface } from "../../../core";
2
+
3
+ export type EmailBlastStatus = "draft" | "sending" | "sent";
4
+
5
+ export type EmailBlastInput = {
6
+ id: string;
7
+ subject: string;
8
+ body: string;
9
+ replyTo?: string;
10
+ };
11
+
12
+ export interface EmailBlastInterface extends ApiDataInterface {
13
+ get subject(): string;
14
+ get body(): string;
15
+ get replyTo(): string | undefined;
16
+ get status(): EmailBlastStatus;
17
+ get sentAt(): Date | undefined;
18
+ get recipientCount(): number | undefined;
19
+ get sentCount(): number;
20
+ get failedCount(): number;
21
+ get unsubscribedCount(): number;
22
+ }
@@ -0,0 +1,101 @@
1
+ import { AbstractService, EndpointCreator, HttpMethod, Modules, NextRef, PreviousRef } from "../../../core";
2
+ import { EMAIL_BLAST_SEGMENTS } from "./email-blast.segments";
3
+ import { EmailBlastAudienceInterface } from "./EmailBlastAudienceInterface";
4
+ import { EmailBlastInput, EmailBlastInterface } from "./EmailBlastInterface";
5
+
6
+ export class EmailBlastService extends AbstractService {
7
+ static async findMany(params: { next?: NextRef; previous?: PreviousRef } = {}): Promise<EmailBlastInterface[]> {
8
+ const endpoint = new EndpointCreator({ endpoint: Modules.EmailBlast });
9
+
10
+ if (Modules.EmailBlast.inclusions?.lists?.fields)
11
+ endpoint.limitToFields(Modules.EmailBlast.inclusions.lists.fields);
12
+ if (Modules.EmailBlast.inclusions?.lists?.types) endpoint.limitToType(Modules.EmailBlast.inclusions.lists.types);
13
+
14
+ return this.callApi<EmailBlastInterface[]>({
15
+ type: Modules.EmailBlast,
16
+ method: HttpMethod.GET,
17
+ endpoint: endpoint.generate(),
18
+ next: params.next,
19
+ previous: params.previous,
20
+ });
21
+ }
22
+
23
+ static async findOne(params: { id: string }): Promise<EmailBlastInterface> {
24
+ return this.callApi<EmailBlastInterface>({
25
+ type: Modules.EmailBlast,
26
+ method: HttpMethod.GET,
27
+ endpoint: new EndpointCreator({ endpoint: Modules.EmailBlast, id: params.id }).generate(),
28
+ });
29
+ }
30
+
31
+ static async create(params: EmailBlastInput): Promise<EmailBlastInterface> {
32
+ return this.callApi<EmailBlastInterface>({
33
+ type: Modules.EmailBlast,
34
+ method: HttpMethod.POST,
35
+ endpoint: new EndpointCreator({ endpoint: Modules.EmailBlast }).generate(),
36
+ input: params,
37
+ });
38
+ }
39
+
40
+ static async update(params: EmailBlastInput): Promise<EmailBlastInterface> {
41
+ return this.callApi<EmailBlastInterface>({
42
+ type: Modules.EmailBlast,
43
+ method: HttpMethod.PATCH,
44
+ endpoint: new EndpointCreator({ endpoint: Modules.EmailBlast, id: params.id }).generate(),
45
+ input: params,
46
+ });
47
+ }
48
+
49
+ static async delete(params: { id: string }): Promise<void> {
50
+ await this.callApi({
51
+ type: Modules.EmailBlast,
52
+ method: HttpMethod.DELETE,
53
+ endpoint: new EndpointCreator({ endpoint: Modules.EmailBlast, id: params.id }).generate(),
54
+ });
55
+ }
56
+
57
+ /**
58
+ * GET email-blasts/:id/recipient-count: how many people the blast would reach now.
59
+ */
60
+ static async findAudience(params: { id: string }): Promise<EmailBlastAudienceInterface> {
61
+ return this.callApi<EmailBlastAudienceInterface>({
62
+ type: Modules.EmailBlastAudience,
63
+ method: HttpMethod.GET,
64
+ endpoint: new EndpointCreator({
65
+ endpoint: Modules.EmailBlast,
66
+ id: params.id,
67
+ childEndpoint: EMAIL_BLAST_SEGMENTS.audience,
68
+ }).generate(),
69
+ });
70
+ }
71
+
72
+ /**
73
+ * POST email-blasts/:id/test: sends the draft to the calling administrator. Replies 204.
74
+ */
75
+ static async sendTest(params: { id: string }): Promise<void> {
76
+ await this.callApi({
77
+ type: Modules.EmailBlast,
78
+ method: HttpMethod.POST,
79
+ endpoint: new EndpointCreator({
80
+ endpoint: Modules.EmailBlast,
81
+ id: params.id,
82
+ childEndpoint: EMAIL_BLAST_SEGMENTS.test,
83
+ }).generate(),
84
+ });
85
+ }
86
+
87
+ /**
88
+ * POST email-blasts/:id/send: starts the send and returns the blast, now `sending`.
89
+ */
90
+ static async send(params: { id: string }): Promise<EmailBlastInterface> {
91
+ return this.callApi<EmailBlastInterface>({
92
+ type: Modules.EmailBlast,
93
+ method: HttpMethod.POST,
94
+ endpoint: new EndpointCreator({
95
+ endpoint: Modules.EmailBlast,
96
+ id: params.id,
97
+ childEndpoint: EMAIL_BLAST_SEGMENTS.send,
98
+ }).generate(),
99
+ });
100
+ }
101
+ }
@@ -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,8 @@
1
+ export enum EmailBlastFields {
2
+ subject = "subject",
3
+ status = "status",
4
+ sentAt = "sentAt",
5
+ sent = "sent",
6
+ failed = "failed",
7
+ unsubscribed = "unsubscribed",
8
+ }
@@ -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);
@@ -0,0 +1,4 @@
1
+ export * from "./EmailBlastModule";
2
+ export * from "./EmailBlastDeliveryModule";
3
+ export * from "./EmailBlastAudienceModule";
4
+ export * from "./data";
@@ -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";