@mobilizehub/payload-plugin 0.1.0 → 0.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 (92) hide show
  1. package/dist/adapters/index.d.ts +1 -0
  2. package/dist/adapters/index.js +3 -0
  3. package/dist/adapters/index.js.map +1 -0
  4. package/dist/adapters/resend-adapter.d.ts +34 -0
  5. package/dist/adapters/resend-adapter.js +219 -0
  6. package/dist/adapters/resend-adapter.js.map +1 -0
  7. package/dist/collections/broadcasts/generateBroadcastsCollection.d.ts +3 -0
  8. package/dist/collections/broadcasts/generateBroadcastsCollection.js +241 -0
  9. package/dist/collections/broadcasts/generateBroadcastsCollection.js.map +1 -0
  10. package/dist/collections/emails/generateEmailsCollection.d.ts +3 -0
  11. package/dist/collections/emails/generateEmailsCollection.js +204 -0
  12. package/dist/collections/emails/generateEmailsCollection.js.map +1 -0
  13. package/dist/collections/emails/hooks/sync-status-from-activity.d.ts +5 -0
  14. package/dist/collections/emails/hooks/sync-status-from-activity.js +64 -0
  15. package/dist/collections/emails/hooks/sync-status-from-activity.js.map +1 -0
  16. package/dist/collections/pages/generatePagesCollection.d.ts +3 -0
  17. package/dist/collections/pages/generatePagesCollection.js +77 -0
  18. package/dist/collections/pages/generatePagesCollection.js.map +1 -0
  19. package/dist/collections/unsubscribe-tokens/generateUnsubscribeTokens.d.ts +2 -0
  20. package/dist/collections/unsubscribe-tokens/generateUnsubscribeTokens.js +48 -0
  21. package/dist/collections/unsubscribe-tokens/generateUnsubscribeTokens.js.map +1 -0
  22. package/dist/components/broadcast-metrics-card.d.ts +7 -0
  23. package/dist/components/broadcast-metrics-card.js +159 -0
  24. package/dist/components/broadcast-metrics-card.js.map +1 -0
  25. package/dist/components/broadcast-send-modal.d.ts +9 -0
  26. package/dist/components/broadcast-send-modal.js +51 -0
  27. package/dist/components/broadcast-send-modal.js.map +1 -0
  28. package/dist/components/broadcast-send-test-drawer.d.ts +7 -0
  29. package/dist/components/broadcast-send-test-drawer.js +154 -0
  30. package/dist/components/broadcast-send-test-drawer.js.map +1 -0
  31. package/dist/components/email-activity.d.ts +4 -0
  32. package/dist/components/email-activity.js +359 -0
  33. package/dist/components/email-activity.js.map +1 -0
  34. package/dist/components/email-preview.d.ts +2 -0
  35. package/dist/components/email-preview.js +95 -0
  36. package/dist/components/email-preview.js.map +1 -0
  37. package/dist/endpoints/sendBroadcastHandler.d.ts +9 -0
  38. package/dist/endpoints/sendBroadcastHandler.js +107 -0
  39. package/dist/endpoints/sendBroadcastHandler.js.map +1 -0
  40. package/dist/endpoints/sendTestBroadcastHandler.d.ts +10 -0
  41. package/dist/endpoints/sendTestBroadcastHandler.js +143 -0
  42. package/dist/endpoints/sendTestBroadcastHandler.js.map +1 -0
  43. package/dist/endpoints/unsubscribeHandler.d.ts +9 -0
  44. package/dist/endpoints/unsubscribeHandler.js +153 -0
  45. package/dist/endpoints/unsubscribeHandler.js.map +1 -0
  46. package/dist/exports/client.d.ts +3 -1
  47. package/dist/exports/client.js +3 -0
  48. package/dist/exports/client.js.map +1 -1
  49. package/dist/exports/rsc.d.ts +2 -1
  50. package/dist/exports/rsc.js +2 -0
  51. package/dist/exports/rsc.js.map +1 -1
  52. package/dist/fields/name.d.ts +5 -0
  53. package/dist/fields/name.js +12 -0
  54. package/dist/fields/name.js.map +1 -0
  55. package/dist/fields/publishedAt.d.ts +5 -0
  56. package/dist/fields/publishedAt.js +16 -0
  57. package/dist/fields/publishedAt.js.map +1 -0
  58. package/dist/fields/slug.d.ts +7 -0
  59. package/dist/fields/slug.js +47 -0
  60. package/dist/fields/slug.js.map +1 -0
  61. package/dist/fields/status.d.ts +5 -0
  62. package/dist/fields/status.js +25 -0
  63. package/dist/fields/status.js.map +1 -0
  64. package/dist/index.js +48 -3
  65. package/dist/index.js.map +1 -1
  66. package/dist/react/index.d.ts +1 -0
  67. package/dist/react/index.js +3 -0
  68. package/dist/react/index.js.map +1 -0
  69. package/dist/react/unsubscribe.d.ts +6 -0
  70. package/dist/react/unsubscribe.js +16 -0
  71. package/dist/react/unsubscribe.js.map +1 -0
  72. package/dist/tasks/sendBroadcastsTask.d.ts +11 -0
  73. package/dist/tasks/sendBroadcastsTask.js +196 -0
  74. package/dist/tasks/sendBroadcastsTask.js.map +1 -0
  75. package/dist/tasks/sendEmailTask.d.ts +9 -0
  76. package/dist/tasks/sendEmailTask.js +167 -0
  77. package/dist/tasks/sendEmailTask.js.map +1 -0
  78. package/dist/types/index.d.ts +133 -1
  79. package/dist/types/index.js.map +1 -1
  80. package/dist/utils/api-response.d.ts +72 -0
  81. package/dist/utils/api-response.js +66 -0
  82. package/dist/utils/api-response.js.map +1 -0
  83. package/dist/utils/email.d.ts +36 -0
  84. package/dist/utils/email.js +40 -0
  85. package/dist/utils/email.js.map +1 -0
  86. package/dist/utils/lexical.d.ts +13 -0
  87. package/dist/utils/lexical.js +27 -0
  88. package/dist/utils/lexical.js.map +1 -0
  89. package/dist/utils/unsubscribe-token.d.ts +67 -0
  90. package/dist/utils/unsubscribe-token.js +103 -0
  91. package/dist/utils/unsubscribe-token.js.map +1 -0
  92. package/package.json +20 -9
@@ -0,0 +1,204 @@
1
+ import { authenticated } from '../../access/authenticated.js';
2
+ import { syncStatusFromActivityBeforeChangeHook } from './hooks/sync-status-from-activity.js';
3
+ export const generateEmailsCollection = (emailsConfig)=>{
4
+ const defaultFields = [
5
+ {
6
+ name: 'providerId',
7
+ type: 'text',
8
+ admin: {
9
+ position: 'sidebar',
10
+ readOnly: true
11
+ }
12
+ },
13
+ {
14
+ name: 'status',
15
+ type: 'select',
16
+ admin: {
17
+ position: 'sidebar',
18
+ readOnly: true
19
+ },
20
+ defaultValue: 'draft',
21
+ options: [
22
+ {
23
+ label: 'Queued',
24
+ value: 'queued'
25
+ },
26
+ {
27
+ label: 'Failed',
28
+ value: 'failed'
29
+ },
30
+ {
31
+ label: 'Sent',
32
+ value: 'sent'
33
+ },
34
+ {
35
+ label: 'Delivered',
36
+ value: 'delivered'
37
+ },
38
+ {
39
+ label: 'Bounced',
40
+ value: 'bounced'
41
+ },
42
+ {
43
+ label: 'Unsubscribed',
44
+ value: 'unsubscribed'
45
+ },
46
+ {
47
+ label: 'Complained',
48
+ value: 'complained'
49
+ }
50
+ ],
51
+ required: true
52
+ },
53
+ {
54
+ name: 'broadcast',
55
+ type: 'relationship',
56
+ admin: {
57
+ position: 'sidebar',
58
+ readOnly: true
59
+ },
60
+ relationTo: emailsConfig.broadcastsOverrides?.slug || 'broadcasts'
61
+ },
62
+ {
63
+ name: 'contact',
64
+ type: 'relationship',
65
+ admin: {
66
+ position: 'sidebar',
67
+ readOnly: true
68
+ },
69
+ relationTo: emailsConfig.contactsOverrides?.slug || 'contacts'
70
+ },
71
+ {
72
+ name: 'activityField',
73
+ type: 'ui',
74
+ admin: {
75
+ components: {
76
+ Field: '@mobilizehub/payload-plugin/rsc#EmailActivityField'
77
+ },
78
+ position: 'sidebar'
79
+ }
80
+ },
81
+ {
82
+ name: 'from',
83
+ type: 'text',
84
+ admin: {
85
+ readOnly: true
86
+ },
87
+ required: true
88
+ },
89
+ {
90
+ name: 'activity',
91
+ type: 'array',
92
+ admin: {
93
+ hidden: true,
94
+ readOnly: true
95
+ },
96
+ fields: [
97
+ {
98
+ name: 'type',
99
+ type: 'select',
100
+ options: [
101
+ {
102
+ label: 'Sent',
103
+ value: 'sent'
104
+ },
105
+ {
106
+ label: 'Delivered',
107
+ value: 'delivered'
108
+ },
109
+ {
110
+ label: 'Opened',
111
+ value: 'opened'
112
+ },
113
+ {
114
+ label: 'Clicked',
115
+ value: 'clicked'
116
+ },
117
+ {
118
+ label: 'Bounced',
119
+ value: 'bounced'
120
+ },
121
+ {
122
+ label: 'Unsubscribed',
123
+ value: 'unsubscribed'
124
+ },
125
+ {
126
+ label: 'Complained',
127
+ value: 'complained'
128
+ }
129
+ ],
130
+ required: true
131
+ },
132
+ {
133
+ name: 'timestamp',
134
+ type: 'date',
135
+ required: true
136
+ }
137
+ ],
138
+ label: ''
139
+ },
140
+ {
141
+ name: 'subject',
142
+ type: 'text',
143
+ admin: {
144
+ readOnly: true
145
+ },
146
+ required: true
147
+ },
148
+ {
149
+ name: 'to',
150
+ type: 'text',
151
+ admin: {
152
+ readOnly: true
153
+ },
154
+ required: true
155
+ },
156
+ {
157
+ name: 'html',
158
+ type: 'text',
159
+ admin: {
160
+ hidden: true,
161
+ readOnly: true
162
+ },
163
+ required: true
164
+ },
165
+ {
166
+ name: 'emailPreview',
167
+ type: 'ui',
168
+ admin: {
169
+ components: {
170
+ Field: '@mobilizehub/payload-plugin/client#EmailPreviewField'
171
+ }
172
+ }
173
+ }
174
+ ];
175
+ const config = {
176
+ ...emailsConfig.emailsOverrides || {},
177
+ slug: emailsConfig.emailsOverrides?.slug || 'emails',
178
+ access: {
179
+ create: ()=>false,
180
+ delete: ()=>false,
181
+ read: authenticated,
182
+ update: ()=>false,
183
+ ...emailsConfig.emailsOverrides?.access || {}
184
+ },
185
+ admin: {
186
+ ...emailsConfig.emailsOverrides?.admin || {},
187
+ hidden: emailsConfig.emailsOverrides?.admin?.hidden || false,
188
+ useAsTitle: emailsConfig.emailsOverrides?.admin?.useAsTitle || 'to'
189
+ },
190
+ fields: emailsConfig.emailsOverrides?.fields ? emailsConfig.emailsOverrides.fields({
191
+ defaultFields
192
+ }) : defaultFields,
193
+ hooks: {
194
+ beforeChange: [
195
+ syncStatusFromActivityBeforeChangeHook,
196
+ ...emailsConfig.emailsOverrides?.hooks?.beforeChange || []
197
+ ],
198
+ ...emailsConfig.emailsOverrides?.hooks || {}
199
+ }
200
+ };
201
+ return config;
202
+ };
203
+
204
+ //# sourceMappingURL=generateEmailsCollection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/collections/emails/generateEmailsCollection.ts"],"sourcesContent":["import type { CollectionConfig, Field } from 'payload'\n\nimport type { MobilizehubPluginConfig } from '../../types/index.js'\n\nimport { authenticated } from '../../access/authenticated.js'\nimport { syncStatusFromActivityBeforeChangeHook } from './hooks/sync-status-from-activity.js'\n\nexport const generateEmailsCollection = (emailsConfig: MobilizehubPluginConfig) => {\n const defaultFields: Field[] = [\n {\n name: 'providerId',\n type: 'text',\n admin: {\n position: 'sidebar',\n readOnly: true,\n },\n },\n {\n name: 'status',\n type: 'select',\n admin: {\n position: 'sidebar',\n readOnly: true,\n },\n defaultValue: 'draft',\n options: [\n {\n label: 'Queued',\n value: 'queued',\n },\n {\n label: 'Failed',\n value: 'failed',\n },\n {\n label: 'Sent',\n value: 'sent',\n },\n {\n label: 'Delivered',\n value: 'delivered',\n },\n {\n label: 'Bounced',\n value: 'bounced',\n },\n {\n label: 'Unsubscribed',\n value: 'unsubscribed',\n },\n {\n label: 'Complained',\n value: 'complained',\n },\n ],\n required: true,\n },\n {\n name: 'broadcast',\n type: 'relationship',\n admin: {\n position: 'sidebar',\n readOnly: true,\n },\n relationTo: emailsConfig.broadcastsOverrides?.slug || 'broadcasts',\n },\n {\n name: 'contact',\n type: 'relationship',\n admin: {\n position: 'sidebar',\n readOnly: true,\n },\n relationTo: emailsConfig.contactsOverrides?.slug || 'contacts',\n },\n\n {\n name: 'activityField',\n type: 'ui',\n admin: {\n components: {\n Field: '@mobilizehub/payload-plugin/rsc#EmailActivityField',\n },\n position: 'sidebar',\n },\n },\n {\n name: 'from',\n type: 'text',\n admin: {\n readOnly: true,\n },\n required: true,\n },\n {\n name: 'activity',\n type: 'array',\n admin: {\n hidden: true,\n readOnly: true,\n },\n fields: [\n {\n name: 'type',\n type: 'select',\n options: [\n { label: 'Sent', value: 'sent' },\n { label: 'Delivered', value: 'delivered' },\n { label: 'Opened', value: 'opened' },\n { label: 'Clicked', value: 'clicked' },\n { label: 'Bounced', value: 'bounced' },\n { label: 'Unsubscribed', value: 'unsubscribed' },\n { label: 'Complained', value: 'complained' },\n ],\n required: true,\n },\n {\n name: 'timestamp',\n type: 'date',\n required: true,\n },\n ],\n label: '',\n },\n {\n name: 'subject',\n type: 'text',\n admin: {\n readOnly: true,\n },\n required: true,\n },\n {\n name: 'to',\n type: 'text',\n admin: {\n readOnly: true,\n },\n required: true,\n },\n {\n name: 'html',\n type: 'text',\n admin: {\n hidden: true,\n readOnly: true,\n },\n required: true,\n },\n {\n name: 'emailPreview',\n type: 'ui',\n admin: {\n components: {\n Field: '@mobilizehub/payload-plugin/client#EmailPreviewField',\n },\n },\n },\n ]\n\n const config: CollectionConfig = {\n ...(emailsConfig.emailsOverrides || {}),\n slug: emailsConfig.emailsOverrides?.slug || 'emails',\n access: {\n create: () => false,\n delete: () => false,\n read: authenticated,\n update: () => false,\n ...(emailsConfig.emailsOverrides?.access || {}),\n },\n admin: {\n ...(emailsConfig.emailsOverrides?.admin || {}),\n hidden: emailsConfig.emailsOverrides?.admin?.hidden || false,\n useAsTitle: emailsConfig.emailsOverrides?.admin?.useAsTitle || 'to',\n },\n fields: emailsConfig.emailsOverrides?.fields\n ? emailsConfig.emailsOverrides.fields({ defaultFields })\n : defaultFields,\n hooks: {\n beforeChange: [\n syncStatusFromActivityBeforeChangeHook,\n ...(emailsConfig.emailsOverrides?.hooks?.beforeChange || []),\n ],\n ...(emailsConfig.emailsOverrides?.hooks || {}),\n },\n }\n\n return config\n}\n"],"names":["authenticated","syncStatusFromActivityBeforeChangeHook","generateEmailsCollection","emailsConfig","defaultFields","name","type","admin","position","readOnly","defaultValue","options","label","value","required","relationTo","broadcastsOverrides","slug","contactsOverrides","components","Field","hidden","fields","config","emailsOverrides","access","create","delete","read","update","useAsTitle","hooks","beforeChange"],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import type { CollectionBeforeChangeHook } from 'payload';
2
+ /**
3
+ * A hook to synchronize the email status field based on activity history before saving.
4
+ */
5
+ export declare const syncStatusFromActivityBeforeChangeHook: CollectionBeforeChangeHook;
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Maps activity types to their corresponding email status.
3
+ * Some activity types (like 'opened', 'clicked') don't change the status.
4
+ */ const activityTypeToStatus = {
5
+ bounced: 'bounced',
6
+ complained: 'complained',
7
+ delivered: 'delivered',
8
+ sent: 'sent',
9
+ unsubscribed: 'unsubscribed'
10
+ };
11
+ /**
12
+ * Defines the priority of statuses for determining the "most significant" status.
13
+ * Higher index = higher priority (terminal/negative states take precedence).
14
+ */ const statusPriority = [
15
+ 'queued',
16
+ 'sent',
17
+ 'delivered',
18
+ 'bounced',
19
+ 'complained',
20
+ 'unsubscribed'
21
+ ];
22
+ function getStatusPriority(status) {
23
+ const index = statusPriority.indexOf(status);
24
+ return index === -1 ? 0 : index;
25
+ }
26
+ /**
27
+ * Determines the email status based on activity history.
28
+ * Uses the activity with the highest priority status.
29
+ */ function getStatusFromActivity(activity) {
30
+ if (!activity || activity.length === 0) {
31
+ return null;
32
+ }
33
+ let highestPriorityStatus = null;
34
+ let highestPriority = -1;
35
+ for (const item of activity){
36
+ const status = activityTypeToStatus[item.type];
37
+ if (status) {
38
+ const priority = getStatusPriority(status);
39
+ if (priority > highestPriority) {
40
+ highestPriority = priority;
41
+ highestPriorityStatus = status;
42
+ }
43
+ }
44
+ }
45
+ return highestPriorityStatus;
46
+ }
47
+ /**
48
+ * A hook to synchronize the email status field based on activity history before saving.
49
+ */ export const syncStatusFromActivityBeforeChangeHook = ({ data })=>{
50
+ const activity = data.activity;
51
+ if (!activity || activity.length === 0) {
52
+ return data;
53
+ }
54
+ const derivedStatus = getStatusFromActivity(activity);
55
+ if (derivedStatus && derivedStatus !== data.status) {
56
+ return {
57
+ ...data,
58
+ status: derivedStatus
59
+ };
60
+ }
61
+ return data;
62
+ };
63
+
64
+ //# sourceMappingURL=sync-status-from-activity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/collections/emails/hooks/sync-status-from-activity.ts"],"sourcesContent":["import type { CollectionBeforeChangeHook } from 'payload'\n\nimport type { EmailActivityType, EmailStatus } from '../../../types/index.js'\n\ntype ActivityItem = {\n timestamp: string\n type: 'unsubscribed' | EmailActivityType\n}\n\n/**\n * Maps activity types to their corresponding email status.\n * Some activity types (like 'opened', 'clicked') don't change the status.\n */\nconst activityTypeToStatus: Partial<Record<'unsubscribed' | EmailActivityType, EmailStatus>> = {\n bounced: 'bounced',\n complained: 'complained',\n delivered: 'delivered',\n sent: 'sent',\n unsubscribed: 'unsubscribed',\n}\n\n/**\n * Defines the priority of statuses for determining the \"most significant\" status.\n * Higher index = higher priority (terminal/negative states take precedence).\n */\nconst statusPriority: EmailStatus[] = [\n 'queued',\n 'sent',\n 'delivered',\n 'bounced',\n 'complained',\n 'unsubscribed',\n]\n\nfunction getStatusPriority(status: EmailStatus): number {\n const index = statusPriority.indexOf(status)\n return index === -1 ? 0 : index\n}\n\n/**\n * Determines the email status based on activity history.\n * Uses the activity with the highest priority status.\n */\nfunction getStatusFromActivity(activity: ActivityItem[]): EmailStatus | null {\n if (!activity || activity.length === 0) {\n return null\n }\n\n let highestPriorityStatus: EmailStatus | null = null\n let highestPriority = -1\n\n for (const item of activity) {\n const status = activityTypeToStatus[item.type]\n if (status) {\n const priority = getStatusPriority(status)\n if (priority > highestPriority) {\n highestPriority = priority\n highestPriorityStatus = status\n }\n }\n }\n\n return highestPriorityStatus\n}\n\n/**\n * A hook to synchronize the email status field based on activity history before saving.\n */\nexport const syncStatusFromActivityBeforeChangeHook: CollectionBeforeChangeHook = ({ data }) => {\n const activity = data.activity as ActivityItem[] | undefined\n\n if (!activity || activity.length === 0) {\n return data\n }\n\n const derivedStatus = getStatusFromActivity(activity)\n\n if (derivedStatus && derivedStatus !== data.status) {\n return {\n ...data,\n status: derivedStatus,\n }\n }\n\n return data\n}\n"],"names":["activityTypeToStatus","bounced","complained","delivered","sent","unsubscribed","statusPriority","getStatusPriority","status","index","indexOf","getStatusFromActivity","activity","length","highestPriorityStatus","highestPriority","item","type","priority","syncStatusFromActivityBeforeChangeHook","data","derivedStatus"],"mappings":"AASA;;;CAGC,GACD,MAAMA,uBAAyF;IAC7FC,SAAS;IACTC,YAAY;IACZC,WAAW;IACXC,MAAM;IACNC,cAAc;AAChB;AAEA;;;CAGC,GACD,MAAMC,iBAAgC;IACpC;IACA;IACA;IACA;IACA;IACA;CACD;AAED,SAASC,kBAAkBC,MAAmB;IAC5C,MAAMC,QAAQH,eAAeI,OAAO,CAACF;IACrC,OAAOC,UAAU,CAAC,IAAI,IAAIA;AAC5B;AAEA;;;CAGC,GACD,SAASE,sBAAsBC,QAAwB;IACrD,IAAI,CAACA,YAAYA,SAASC,MAAM,KAAK,GAAG;QACtC,OAAO;IACT;IAEA,IAAIC,wBAA4C;IAChD,IAAIC,kBAAkB,CAAC;IAEvB,KAAK,MAAMC,QAAQJ,SAAU;QAC3B,MAAMJ,SAASR,oBAAoB,CAACgB,KAAKC,IAAI,CAAC;QAC9C,IAAIT,QAAQ;YACV,MAAMU,WAAWX,kBAAkBC;YACnC,IAAIU,WAAWH,iBAAiB;gBAC9BA,kBAAkBG;gBAClBJ,wBAAwBN;YAC1B;QACF;IACF;IAEA,OAAOM;AACT;AAEA;;CAEC,GACD,OAAO,MAAMK,yCAAqE,CAAC,EAAEC,IAAI,EAAE;IACzF,MAAMR,WAAWQ,KAAKR,QAAQ;IAE9B,IAAI,CAACA,YAAYA,SAASC,MAAM,KAAK,GAAG;QACtC,OAAOO;IACT;IAEA,MAAMC,gBAAgBV,sBAAsBC;IAE5C,IAAIS,iBAAiBA,kBAAkBD,KAAKZ,MAAM,EAAE;QAClD,OAAO;YACL,GAAGY,IAAI;YACPZ,QAAQa;QACV;IACF;IAEA,OAAOD;AACT,EAAC"}
@@ -0,0 +1,3 @@
1
+ import type { CollectionConfig } from 'payload';
2
+ import type { MobilizehubPluginConfig } from '../../types/index.js';
3
+ export declare const generatePagesCollection: (pagesConfig: MobilizehubPluginConfig) => CollectionConfig;
@@ -0,0 +1,77 @@
1
+ import { createNameField } from '../../fields/name.js';
2
+ import { createPublishedAtField } from '../../fields/publishedAt.js';
3
+ import { createSlugField } from '../../fields/slug.js';
4
+ import { createStatusField } from '../../fields/status.js';
5
+ export const generatePagesCollection = (pagesConfig)=>{
6
+ const defaultBlocks = [
7
+ {
8
+ slug: 'content',
9
+ fields: [
10
+ {
11
+ name: 'richText',
12
+ type: 'richText',
13
+ label: false
14
+ }
15
+ ],
16
+ interfaceName: 'ContentBlock'
17
+ }
18
+ ];
19
+ const blocks = pagesConfig.pagesOverrides?.blocks ? pagesConfig.pagesOverrides.blocks({
20
+ defaultBlocks
21
+ }) : defaultBlocks;
22
+ const defaultFields = [
23
+ createStatusField(),
24
+ {
25
+ type: 'tabs',
26
+ tabs: [
27
+ {
28
+ fields: [
29
+ createNameField(),
30
+ createSlugField(),
31
+ createPublishedAtField()
32
+ ],
33
+ label: 'Settings'
34
+ },
35
+ {
36
+ fields: [
37
+ {
38
+ name: 'blocks',
39
+ type: 'blocks',
40
+ blocks,
41
+ label: 'Blocks'
42
+ }
43
+ ],
44
+ label: 'Content'
45
+ }
46
+ ]
47
+ }
48
+ ];
49
+ const config = {
50
+ slug: pagesConfig.pagesOverrides?.slug || 'pages',
51
+ access: {
52
+ read: ()=>true,
53
+ ...pagesConfig.pagesOverrides?.access || {}
54
+ },
55
+ admin: {
56
+ ...pagesConfig.pagesOverrides?.admin || {},
57
+ defaultColumns: pagesConfig.pagesOverrides?.admin?.defaultColumns || [
58
+ 'id',
59
+ 'name',
60
+ 'slug',
61
+ 'status'
62
+ ],
63
+ hidden: pagesConfig.pagesOverrides?.admin?.hidden || false,
64
+ useAsTitle: pagesConfig.pagesOverrides?.admin?.useAsTitle || 'name'
65
+ },
66
+ fields: pagesConfig.pagesOverrides?.fields ? pagesConfig.pagesOverrides.fields({
67
+ defaultFields
68
+ }) : defaultFields,
69
+ hooks: {
70
+ ...pagesConfig.pagesOverrides?.hooks || {}
71
+ },
72
+ ...pagesConfig.pagesOverrides = {}
73
+ };
74
+ return config;
75
+ };
76
+
77
+ //# sourceMappingURL=generatePagesCollection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/collections/pages/generatePagesCollection.ts"],"sourcesContent":["import type { Block, CollectionConfig, Field } from 'payload'\n\nimport type { MobilizehubPluginConfig } from '../../types/index.js'\n\nimport { createNameField } from '../../fields/name.js'\nimport { createPublishedAtField } from '../../fields/publishedAt.js'\nimport { createSlugField } from '../../fields/slug.js'\nimport { createStatusField } from '../../fields/status.js'\n\nexport const generatePagesCollection = (pagesConfig: MobilizehubPluginConfig) => {\n const defaultBlocks: Block[] = [\n {\n slug: 'content',\n fields: [\n {\n name: 'richText',\n type: 'richText',\n label: false,\n },\n ],\n interfaceName: 'ContentBlock',\n },\n ]\n\n const blocks = pagesConfig.pagesOverrides?.blocks\n ? pagesConfig.pagesOverrides.blocks({ defaultBlocks })\n : defaultBlocks\n\n const defaultFields: Field[] = [\n createStatusField(),\n {\n type: 'tabs',\n tabs: [\n {\n fields: [createNameField(), createSlugField(), createPublishedAtField()],\n label: 'Settings',\n },\n {\n fields: [\n {\n name: 'blocks',\n type: 'blocks',\n blocks,\n label: 'Blocks',\n },\n ],\n label: 'Content',\n },\n ],\n },\n ]\n\n const config: CollectionConfig = {\n slug: pagesConfig.pagesOverrides?.slug || 'pages',\n access: {\n read: () => true,\n ...(pagesConfig.pagesOverrides?.access || {}),\n },\n admin: {\n ...(pagesConfig.pagesOverrides?.admin || {}),\n defaultColumns: pagesConfig.pagesOverrides?.admin?.defaultColumns || [\n 'id',\n 'name',\n 'slug',\n 'status',\n ],\n hidden: pagesConfig.pagesOverrides?.admin?.hidden || false,\n useAsTitle: pagesConfig.pagesOverrides?.admin?.useAsTitle || 'name',\n },\n fields: pagesConfig.pagesOverrides?.fields\n ? pagesConfig.pagesOverrides.fields({ defaultFields })\n : defaultFields,\n hooks: {\n ...(pagesConfig.pagesOverrides?.hooks || {}),\n },\n ...(pagesConfig.pagesOverrides = {}),\n }\n\n return config\n}\n"],"names":["createNameField","createPublishedAtField","createSlugField","createStatusField","generatePagesCollection","pagesConfig","defaultBlocks","slug","fields","name","type","label","interfaceName","blocks","pagesOverrides","defaultFields","tabs","config","access","read","admin","defaultColumns","hidden","useAsTitle","hooks"],"mappings":"AAIA,SAASA,eAAe,QAAQ,uBAAsB;AACtD,SAASC,sBAAsB,QAAQ,8BAA6B;AACpE,SAASC,eAAe,QAAQ,uBAAsB;AACtD,SAASC,iBAAiB,QAAQ,yBAAwB;AAE1D,OAAO,MAAMC,0BAA0B,CAACC;IACtC,MAAMC,gBAAyB;QAC7B;YACEC,MAAM;YACNC,QAAQ;gBACN;oBACEC,MAAM;oBACNC,MAAM;oBACNC,OAAO;gBACT;aACD;YACDC,eAAe;QACjB;KACD;IAED,MAAMC,SAASR,YAAYS,cAAc,EAAED,SACvCR,YAAYS,cAAc,CAACD,MAAM,CAAC;QAAEP;IAAc,KAClDA;IAEJ,MAAMS,gBAAyB;QAC7BZ;QACA;YACEO,MAAM;YACNM,MAAM;gBACJ;oBACER,QAAQ;wBAACR;wBAAmBE;wBAAmBD;qBAAyB;oBACxEU,OAAO;gBACT;gBACA;oBACEH,QAAQ;wBACN;4BACEC,MAAM;4BACNC,MAAM;4BACNG;4BACAF,OAAO;wBACT;qBACD;oBACDA,OAAO;gBACT;aACD;QACH;KACD;IAED,MAAMM,SAA2B;QAC/BV,MAAMF,YAAYS,cAAc,EAAEP,QAAQ;QAC1CW,QAAQ;YACNC,MAAM,IAAM;YACZ,GAAId,YAAYS,cAAc,EAAEI,UAAU,CAAC,CAAC;QAC9C;QACAE,OAAO;YACL,GAAIf,YAAYS,cAAc,EAAEM,SAAS,CAAC,CAAC;YAC3CC,gBAAgBhB,YAAYS,cAAc,EAAEM,OAAOC,kBAAkB;gBACnE;gBACA;gBACA;gBACA;aACD;YACDC,QAAQjB,YAAYS,cAAc,EAAEM,OAAOE,UAAU;YACrDC,YAAYlB,YAAYS,cAAc,EAAEM,OAAOG,cAAc;QAC/D;QACAf,QAAQH,YAAYS,cAAc,EAAEN,SAChCH,YAAYS,cAAc,CAACN,MAAM,CAAC;YAAEO;QAAc,KAClDA;QACJS,OAAO;YACL,GAAInB,YAAYS,cAAc,EAAEU,SAAS,CAAC,CAAC;QAC7C;QACA,GAAInB,YAAYS,cAAc,GAAG,CAAC,CAAC;IACrC;IAEA,OAAOG;AACT,EAAC"}
@@ -0,0 +1,2 @@
1
+ import type { CollectionConfig } from 'payload';
2
+ export declare const generateUnsubscribeTokensCollection: () => CollectionConfig;
@@ -0,0 +1,48 @@
1
+ export const generateUnsubscribeTokensCollection = ()=>{
2
+ const defaultFields = [
3
+ {
4
+ name: 'id',
5
+ type: 'text',
6
+ required: true,
7
+ unique: true
8
+ },
9
+ {
10
+ name: 'emailId',
11
+ type: 'relationship',
12
+ relationTo: 'emails'
13
+ },
14
+ {
15
+ name: 'expiresAt',
16
+ type: 'date'
17
+ }
18
+ ];
19
+ const config = {
20
+ slug: 'emailUnsubscribeTokens',
21
+ access: {
22
+ create: ()=>false,
23
+ delete: ()=>false,
24
+ read: ()=>false,
25
+ update: ()=>false
26
+ },
27
+ admin: {
28
+ hidden: true
29
+ },
30
+ fields: defaultFields,
31
+ hooks: {
32
+ beforeChange: [
33
+ ({ data, operation })=>{
34
+ if (operation === 'create') {
35
+ if (!data.expiresAt) {
36
+ const expiresAt = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000);
37
+ data.expiresAt = expiresAt.toISOString();
38
+ }
39
+ }
40
+ return data;
41
+ }
42
+ ]
43
+ }
44
+ };
45
+ return config;
46
+ };
47
+
48
+ //# sourceMappingURL=generateUnsubscribeTokens.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/collections/unsubscribe-tokens/generateUnsubscribeTokens.ts"],"sourcesContent":["import type { CollectionConfig, Field } from 'payload'\n\nexport const generateUnsubscribeTokensCollection = () => {\n const defaultFields: Field[] = [\n {\n name: 'id',\n type: 'text',\n required: true,\n unique: true,\n },\n {\n name: 'emailId',\n type: 'relationship',\n relationTo: 'emails',\n },\n {\n name: 'expiresAt',\n type: 'date',\n },\n ]\n\n const config: CollectionConfig = {\n slug: 'emailUnsubscribeTokens',\n access: {\n create: () => false,\n delete: () => false,\n read: () => false,\n update: () => false,\n },\n admin: {\n hidden: true,\n },\n fields: defaultFields,\n hooks: {\n beforeChange: [\n ({ data, operation }) => {\n if (operation === 'create') {\n if (!data.expiresAt) {\n const expiresAt = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000)\n data.expiresAt = expiresAt.toISOString()\n }\n }\n return data\n },\n ],\n },\n }\n\n return config\n}\n"],"names":["generateUnsubscribeTokensCollection","defaultFields","name","type","required","unique","relationTo","config","slug","access","create","delete","read","update","admin","hidden","fields","hooks","beforeChange","data","operation","expiresAt","Date","now","toISOString"],"mappings":"AAEA,OAAO,MAAMA,sCAAsC;IACjD,MAAMC,gBAAyB;QAC7B;YACEC,MAAM;YACNC,MAAM;YACNC,UAAU;YACVC,QAAQ;QACV;QACA;YACEH,MAAM;YACNC,MAAM;YACNG,YAAY;QACd;QACA;YACEJ,MAAM;YACNC,MAAM;QACR;KACD;IAED,MAAMI,SAA2B;QAC/BC,MAAM;QACNC,QAAQ;YACNC,QAAQ,IAAM;YACdC,QAAQ,IAAM;YACdC,MAAM,IAAM;YACZC,QAAQ,IAAM;QAChB;QACAC,OAAO;YACLC,QAAQ;QACV;QACAC,QAAQf;QACRgB,OAAO;YACLC,cAAc;gBACZ,CAAC,EAAEC,IAAI,EAAEC,SAAS,EAAE;oBAClB,IAAIA,cAAc,UAAU;wBAC1B,IAAI,CAACD,KAAKE,SAAS,EAAE;4BACnB,MAAMA,YAAY,IAAIC,KAAKA,KAAKC,GAAG,KAAK,KAAK,KAAK,KAAK,KAAK;4BAC5DJ,KAAKE,SAAS,GAAGA,UAAUG,WAAW;wBACxC;oBACF;oBACA,OAAOL;gBACT;aACD;QACH;IACF;IAEA,OAAOZ;AACT,EAAC"}
@@ -0,0 +1,7 @@
1
+ import type { Payload } from 'payload';
2
+ export declare const MetricsCards: ({ data, payload, }: {
3
+ data: {
4
+ id: number;
5
+ };
6
+ payload: Payload;
7
+ }) => Promise<import("react").JSX.Element | null>;
@@ -0,0 +1,159 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ function roundToTwo(num) {
3
+ return Math.round(num * 100) / 100;
4
+ }
5
+ export const MetricsCards = async ({ data, payload })=>{
6
+ const broadcastId = data.id;
7
+ if (!broadcastId) {
8
+ return null;
9
+ }
10
+ const broadcast = await payload.find({
11
+ collection: 'broadcasts',
12
+ limit: 1,
13
+ where: {
14
+ id: {
15
+ equals: broadcastId
16
+ }
17
+ }
18
+ });
19
+ if (!broadcast.totalDocs) {
20
+ return null;
21
+ }
22
+ const contacts = broadcast.docs[0].meta.contactsCount;
23
+ // Run all count queries in parallel for better performance
24
+ const [{ totalDocs: emails }, { totalDocs: delivered }, { totalDocs: bounced }, { totalDocs: unsubscribed }, { totalDocs: complained }] = await Promise.all([
25
+ payload.count({
26
+ collection: 'emails',
27
+ where: {
28
+ broadcast: {
29
+ equals: broadcastId
30
+ }
31
+ }
32
+ }),
33
+ payload.count({
34
+ collection: 'emails',
35
+ where: {
36
+ broadcast: {
37
+ equals: broadcastId
38
+ },
39
+ status: {
40
+ equals: 'delivered'
41
+ }
42
+ }
43
+ }),
44
+ payload.count({
45
+ collection: 'emails',
46
+ where: {
47
+ broadcast: {
48
+ equals: broadcastId
49
+ },
50
+ status: {
51
+ equals: 'bounced'
52
+ }
53
+ }
54
+ }),
55
+ payload.count({
56
+ collection: 'emails',
57
+ where: {
58
+ broadcast: {
59
+ equals: broadcastId
60
+ },
61
+ status: {
62
+ equals: 'unsubscribed'
63
+ }
64
+ }
65
+ }),
66
+ payload.count({
67
+ collection: 'emails',
68
+ where: {
69
+ broadcast: {
70
+ equals: broadcastId
71
+ },
72
+ status: {
73
+ equals: 'complained'
74
+ }
75
+ }
76
+ })
77
+ ]);
78
+ return /*#__PURE__*/ _jsxs("div", {
79
+ style: {
80
+ marginBottom: '2.5rem'
81
+ },
82
+ children: [
83
+ /*#__PURE__*/ _jsx("div", {
84
+ style: {
85
+ marginBottom: '1rem'
86
+ },
87
+ children: /*#__PURE__*/ _jsx("p", {
88
+ children: "Metrics"
89
+ })
90
+ }),
91
+ /*#__PURE__*/ _jsx("div", {
92
+ className: "metrics-grid",
93
+ style: {
94
+ display: 'grid',
95
+ gap: '1.5rem',
96
+ gridTemplateColumns: 'repeat(auto-fit, minmax(240px, 1fr))'
97
+ },
98
+ children: [
99
+ {
100
+ name: 'Contacts',
101
+ value: contacts
102
+ },
103
+ {
104
+ name: 'Emails',
105
+ value: emails
106
+ },
107
+ {
108
+ name: 'Delivered',
109
+ value: delivered
110
+ },
111
+ {
112
+ name: 'Bounced',
113
+ value: bounced
114
+ },
115
+ {
116
+ name: 'Unsubscribed',
117
+ value: unsubscribed
118
+ },
119
+ {
120
+ name: 'Complained',
121
+ value: complained
122
+ }
123
+ ].map((stat)=>/*#__PURE__*/ _jsxs("div", {
124
+ className: "card",
125
+ children: [
126
+ /*#__PURE__*/ _jsxs("div", {
127
+ children: [
128
+ /*#__PURE__*/ _jsx("dt", {
129
+ className: "card__title",
130
+ children: stat.name
131
+ }),
132
+ /*#__PURE__*/ _jsx("dd", {
133
+ style: {
134
+ color: 'var(--theme-elevation-1000)',
135
+ fontSize: '1.5rem',
136
+ fontWeight: '600',
137
+ letterSpacing: '-0.025em',
138
+ lineHeight: '2.5rem',
139
+ margin: 0
140
+ },
141
+ children: stat.value.toLocaleString()
142
+ })
143
+ ]
144
+ }),
145
+ stat.name !== 'Emails' && stat.name !== 'Contacts' && emails > 0 && /*#__PURE__*/ _jsxs("dd", {
146
+ className: "card__action",
147
+ children: [
148
+ roundToTwo(stat.value / emails * 100),
149
+ "%"
150
+ ]
151
+ })
152
+ ]
153
+ }, stat.name))
154
+ })
155
+ ]
156
+ });
157
+ };
158
+
159
+ //# sourceMappingURL=broadcast-metrics-card.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/broadcast-metrics-card.tsx"],"sourcesContent":["import type { Payload } from 'payload'\n\nfunction roundToTwo(num: number): number {\n return Math.round(num * 100) / 100\n}\n\nexport const MetricsCards = async ({\n data,\n payload,\n}: {\n data: { id: number }\n payload: Payload\n}) => {\n const broadcastId = data.id\n\n if (!broadcastId) {\n return null\n }\n\n const broadcast = await payload.find({\n collection: 'broadcasts',\n limit: 1,\n where: {\n id: {\n equals: broadcastId,\n },\n },\n })\n\n if (!broadcast.totalDocs) {\n return null\n }\n\n const contacts = broadcast.docs[0].meta.contactsCount as number\n\n // Run all count queries in parallel for better performance\n const [\n { totalDocs: emails },\n { totalDocs: delivered },\n { totalDocs: bounced },\n { totalDocs: unsubscribed },\n { totalDocs: complained },\n ] = await Promise.all([\n payload.count({\n collection: 'emails',\n where: {\n broadcast: { equals: broadcastId },\n },\n }),\n payload.count({\n collection: 'emails',\n where: {\n broadcast: { equals: broadcastId },\n status: { equals: 'delivered' },\n },\n }),\n payload.count({\n collection: 'emails',\n where: {\n broadcast: { equals: broadcastId },\n status: { equals: 'bounced' },\n },\n }),\n payload.count({\n collection: 'emails',\n where: {\n broadcast: { equals: broadcastId },\n status: { equals: 'unsubscribed' },\n },\n }),\n payload.count({\n collection: 'emails',\n where: {\n broadcast: { equals: broadcastId },\n status: { equals: 'complained' },\n },\n }),\n ])\n\n return (\n <div style={{ marginBottom: '2.5rem' }}>\n <div style={{ marginBottom: '1rem' }}>\n <p>Metrics</p>\n </div>\n <div\n className=\"metrics-grid\"\n style={{\n display: 'grid',\n gap: '1.5rem',\n gridTemplateColumns: 'repeat(auto-fit, minmax(240px, 1fr))',\n }}\n >\n {[\n { name: 'Contacts', value: contacts },\n { name: 'Emails', value: emails },\n { name: 'Delivered', value: delivered },\n { name: 'Bounced', value: bounced },\n { name: 'Unsubscribed', value: unsubscribed },\n { name: 'Complained', value: complained },\n ].map((stat) => (\n <div className=\"card\" key={stat.name}>\n <div>\n <dt className=\"card__title\">{stat.name}</dt>\n <dd\n style={{\n color: 'var(--theme-elevation-1000)',\n fontSize: '1.5rem',\n fontWeight: '600',\n letterSpacing: '-0.025em',\n lineHeight: '2.5rem',\n margin: 0,\n }}\n >\n {stat.value.toLocaleString()}\n </dd>\n </div>\n {stat.name !== 'Emails' && stat.name !== 'Contacts' && emails > 0 && (\n <dd className=\"card__action\">{roundToTwo((stat.value / emails) * 100)}%</dd>\n )}\n </div>\n ))}\n </div>\n </div>\n )\n}\n"],"names":["roundToTwo","num","Math","round","MetricsCards","data","payload","broadcastId","id","broadcast","find","collection","limit","where","equals","totalDocs","contacts","docs","meta","contactsCount","emails","delivered","bounced","unsubscribed","complained","Promise","all","count","status","div","style","marginBottom","p","className","display","gap","gridTemplateColumns","name","value","map","stat","dt","dd","color","fontSize","fontWeight","letterSpacing","lineHeight","margin","toLocaleString"],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ interface SendBroadcastModalProps {
3
+ buttonLabel?: string;
4
+ modalBody?: string;
5
+ modalSlug?: string;
6
+ modalTitle?: string;
7
+ }
8
+ export declare const SendBroadcastModal: React.FC<SendBroadcastModalProps>;
9
+ export {};