payload-plugin-newsletter 0.4.4 → 0.6.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/CHANGELOG.md +28 -1
- package/ESM_FIX_SUMMARY.md +74 -0
- package/dist/client.cjs +899 -0
- package/dist/client.cjs.map +1 -0
- package/dist/client.d.cts +52 -0
- package/dist/client.d.ts +52 -0
- package/dist/client.js +867 -0
- package/dist/client.js.map +1 -0
- package/dist/components.cjs +899 -0
- package/dist/components.cjs.map +1 -0
- package/dist/components.d.cts +4 -0
- package/dist/components.d.ts +4 -0
- package/dist/components.js +867 -0
- package/dist/components.js.map +1 -0
- package/dist/index.cjs +1928 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +11 -0
- package/dist/index.d.ts +6 -5
- package/dist/index.js +1891 -0
- package/dist/index.js.map +1 -0
- package/dist/types.cjs +19 -0
- package/dist/types.cjs.map +1 -0
- package/dist/{types/index.d.ts → types.d.cts} +20 -18
- package/dist/types.d.ts +350 -0
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -0
- package/package.json +40 -17
- package/dist/.tsbuildinfo +0 -1
- package/dist/collections/NewsletterSettings.d.ts +0 -4
- package/dist/collections/NewsletterSettings.d.ts.map +0 -1
- package/dist/collections/Subscribers.d.ts +0 -4
- package/dist/collections/Subscribers.d.ts.map +0 -1
- package/dist/components/MagicLinkVerify.d.ts +0 -27
- package/dist/components/MagicLinkVerify.d.ts.map +0 -1
- package/dist/components/NewsletterForm.d.ts +0 -5
- package/dist/components/NewsletterForm.d.ts.map +0 -1
- package/dist/components/PreferencesForm.d.ts +0 -5
- package/dist/components/PreferencesForm.d.ts.map +0 -1
- package/dist/components/index.d.ts +0 -5
- package/dist/components/index.d.ts.map +0 -1
- package/dist/endpoints/index.d.ts +0 -4
- package/dist/endpoints/index.d.ts.map +0 -1
- package/dist/endpoints/preferences.d.ts +0 -5
- package/dist/endpoints/preferences.d.ts.map +0 -1
- package/dist/endpoints/subscribe.d.ts +0 -4
- package/dist/endpoints/subscribe.d.ts.map +0 -1
- package/dist/endpoints/unsubscribe.d.ts +0 -4
- package/dist/endpoints/unsubscribe.d.ts.map +0 -1
- package/dist/endpoints/verify-magic-link.d.ts +0 -4
- package/dist/endpoints/verify-magic-link.d.ts.map +0 -1
- package/dist/exports/client.d.ts +0 -6
- package/dist/exports/client.d.ts.map +0 -1
- package/dist/exports/components.d.ts +0 -2
- package/dist/exports/components.d.ts.map +0 -1
- package/dist/exports/types.d.ts +0 -2
- package/dist/exports/types.d.ts.map +0 -1
- package/dist/fields/newsletterScheduling.d.ts +0 -4
- package/dist/fields/newsletterScheduling.d.ts.map +0 -1
- package/dist/hooks/useNewsletterAuth.d.ts +0 -16
- package/dist/hooks/useNewsletterAuth.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/providers/broadcast.d.ts +0 -19
- package/dist/providers/broadcast.d.ts.map +0 -1
- package/dist/providers/index.d.ts +0 -23
- package/dist/providers/index.d.ts.map +0 -1
- package/dist/providers/resend.d.ts +0 -20
- package/dist/providers/resend.d.ts.map +0 -1
- package/dist/providers/types.d.ts +0 -46
- package/dist/providers/types.d.ts.map +0 -1
- package/dist/src/collections/NewsletterSettings.js +0 -390
- package/dist/src/collections/NewsletterSettings.js.map +0 -1
- package/dist/src/collections/Subscribers.js +0 -309
- package/dist/src/collections/Subscribers.js.map +0 -1
- package/dist/src/components/MagicLinkVerify.js +0 -180
- package/dist/src/components/MagicLinkVerify.js.map +0 -1
- package/dist/src/components/NewsletterForm.js +0 -326
- package/dist/src/components/NewsletterForm.js.map +0 -1
- package/dist/src/components/PreferencesForm.js +0 -524
- package/dist/src/components/PreferencesForm.js.map +0 -1
- package/dist/src/components/index.js +0 -5
- package/dist/src/components/index.js.map +0 -1
- package/dist/src/endpoints/index.js +0 -17
- package/dist/src/endpoints/index.js.map +0 -1
- package/dist/src/endpoints/preferences.js +0 -136
- package/dist/src/endpoints/preferences.js.map +0 -1
- package/dist/src/endpoints/subscribe.js +0 -162
- package/dist/src/endpoints/subscribe.js.map +0 -1
- package/dist/src/endpoints/unsubscribe.js +0 -105
- package/dist/src/endpoints/unsubscribe.js.map +0 -1
- package/dist/src/endpoints/verify-magic-link.js +0 -103
- package/dist/src/endpoints/verify-magic-link.js.map +0 -1
- package/dist/src/exports/client.js +0 -7
- package/dist/src/exports/client.js.map +0 -1
- package/dist/src/exports/components.js +0 -6
- package/dist/src/exports/components.js.map +0 -1
- package/dist/src/exports/types.js +0 -3
- package/dist/src/exports/types.js.map +0 -1
- package/dist/src/fields/newsletterScheduling.js +0 -194
- package/dist/src/fields/newsletterScheduling.js.map +0 -1
- package/dist/src/hooks/useNewsletterAuth.js +0 -112
- package/dist/src/hooks/useNewsletterAuth.js.map +0 -1
- package/dist/src/index.js +0 -130
- package/dist/src/index.js.map +0 -1
- package/dist/src/providers/broadcast.js +0 -158
- package/dist/src/providers/broadcast.js.map +0 -1
- package/dist/src/providers/index.js +0 -63
- package/dist/src/providers/index.js.map +0 -1
- package/dist/src/providers/resend.js +0 -122
- package/dist/src/providers/resend.js.map +0 -1
- package/dist/src/providers/types.js +0 -12
- package/dist/src/providers/types.js.map +0 -1
- package/dist/src/templates/BaseTemplate.js +0 -105
- package/dist/src/templates/BaseTemplate.js.map +0 -1
- package/dist/src/templates/MagicLinkTemplate.js +0 -178
- package/dist/src/templates/MagicLinkTemplate.js.map +0 -1
- package/dist/src/templates/NewsletterTemplate.js +0 -150
- package/dist/src/templates/NewsletterTemplate.js.map +0 -1
- package/dist/src/templates/WelcomeTemplate.js +0 -192
- package/dist/src/templates/WelcomeTemplate.js.map +0 -1
- package/dist/src/templates/index.js +0 -6
- package/dist/src/templates/index.js.map +0 -1
- package/dist/src/types/index.js +0 -3
- package/dist/src/types/index.js.map +0 -1
- package/dist/src/utils/access.js +0 -80
- package/dist/src/utils/access.js.map +0 -1
- package/dist/src/utils/jwt.js +0 -91
- package/dist/src/utils/jwt.js.map +0 -1
- package/dist/src/utils/rate-limiter.js +0 -43
- package/dist/src/utils/rate-limiter.js.map +0 -1
- package/dist/src/utils/validation.js +0 -148
- package/dist/src/utils/validation.js.map +0 -1
- package/dist/templates/BaseTemplate.d.ts +0 -45
- package/dist/templates/BaseTemplate.d.ts.map +0 -1
- package/dist/templates/MagicLinkTemplate.d.ts +0 -67
- package/dist/templates/MagicLinkTemplate.d.ts.map +0 -1
- package/dist/templates/NewsletterTemplate.d.ts +0 -112
- package/dist/templates/NewsletterTemplate.d.ts.map +0 -1
- package/dist/templates/WelcomeTemplate.d.ts +0 -55
- package/dist/templates/WelcomeTemplate.d.ts.map +0 -1
- package/dist/templates/index.d.ts +0 -7
- package/dist/templates/index.d.ts.map +0 -1
- package/dist/types/index.d.ts.map +0 -1
- package/dist/utils/access.d.ts +0 -15
- package/dist/utils/access.d.ts.map +0 -1
- package/dist/utils/jwt.d.ts +0 -32
- package/dist/utils/jwt.d.ts.map +0 -1
- package/dist/utils/rate-limiter.d.ts +0 -15
- package/dist/utils/rate-limiter.d.ts.map +0 -1
- package/dist/utils/validation.d.ts +0 -33
- package/dist/utils/validation.d.ts.map +0 -1
|
@@ -1,309 +0,0 @@
|
|
|
1
|
-
import { adminOnly, adminOrSelf } from '../utils/access';
|
|
2
|
-
export const createSubscribersCollection = (pluginConfig)=>{
|
|
3
|
-
const slug = pluginConfig.subscribersSlug || 'subscribers';
|
|
4
|
-
// Default fields for the subscribers collection
|
|
5
|
-
const defaultFields = [
|
|
6
|
-
// Core fields
|
|
7
|
-
{
|
|
8
|
-
name: 'email',
|
|
9
|
-
type: 'email',
|
|
10
|
-
required: true,
|
|
11
|
-
unique: true,
|
|
12
|
-
admin: {
|
|
13
|
-
description: 'Subscriber email address'
|
|
14
|
-
}
|
|
15
|
-
},
|
|
16
|
-
{
|
|
17
|
-
name: 'name',
|
|
18
|
-
type: 'text',
|
|
19
|
-
admin: {
|
|
20
|
-
description: 'Subscriber full name'
|
|
21
|
-
}
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
name: 'locale',
|
|
25
|
-
type: 'select',
|
|
26
|
-
options: pluginConfig.i18n?.locales?.map((locale)=>({
|
|
27
|
-
label: locale.toUpperCase(),
|
|
28
|
-
value: locale
|
|
29
|
-
})) || [
|
|
30
|
-
{
|
|
31
|
-
label: 'EN',
|
|
32
|
-
value: 'en'
|
|
33
|
-
}
|
|
34
|
-
],
|
|
35
|
-
defaultValue: pluginConfig.i18n?.defaultLocale || 'en',
|
|
36
|
-
admin: {
|
|
37
|
-
description: 'Preferred language for communications'
|
|
38
|
-
}
|
|
39
|
-
},
|
|
40
|
-
// Authentication fields (hidden from admin UI)
|
|
41
|
-
{
|
|
42
|
-
name: 'magicLinkToken',
|
|
43
|
-
type: 'text',
|
|
44
|
-
hidden: true
|
|
45
|
-
},
|
|
46
|
-
{
|
|
47
|
-
name: 'magicLinkTokenExpiry',
|
|
48
|
-
type: 'date',
|
|
49
|
-
hidden: true
|
|
50
|
-
},
|
|
51
|
-
// Subscription status
|
|
52
|
-
{
|
|
53
|
-
name: 'subscriptionStatus',
|
|
54
|
-
type: 'select',
|
|
55
|
-
options: [
|
|
56
|
-
{
|
|
57
|
-
label: 'Active',
|
|
58
|
-
value: 'active'
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
label: 'Unsubscribed',
|
|
62
|
-
value: 'unsubscribed'
|
|
63
|
-
},
|
|
64
|
-
{
|
|
65
|
-
label: 'Pending',
|
|
66
|
-
value: 'pending'
|
|
67
|
-
}
|
|
68
|
-
],
|
|
69
|
-
defaultValue: 'pending',
|
|
70
|
-
required: true,
|
|
71
|
-
admin: {
|
|
72
|
-
description: 'Current subscription status'
|
|
73
|
-
}
|
|
74
|
-
},
|
|
75
|
-
{
|
|
76
|
-
name: 'unsubscribedAt',
|
|
77
|
-
type: 'date',
|
|
78
|
-
admin: {
|
|
79
|
-
condition: (data)=>data?.subscriptionStatus === 'unsubscribed',
|
|
80
|
-
description: 'When the user unsubscribed',
|
|
81
|
-
readOnly: true
|
|
82
|
-
}
|
|
83
|
-
},
|
|
84
|
-
// Email preferences
|
|
85
|
-
{
|
|
86
|
-
name: 'emailPreferences',
|
|
87
|
-
type: 'group',
|
|
88
|
-
fields: [
|
|
89
|
-
{
|
|
90
|
-
name: 'newsletter',
|
|
91
|
-
type: 'checkbox',
|
|
92
|
-
defaultValue: true,
|
|
93
|
-
label: 'Newsletter',
|
|
94
|
-
admin: {
|
|
95
|
-
description: 'Receive regular newsletter updates'
|
|
96
|
-
}
|
|
97
|
-
},
|
|
98
|
-
{
|
|
99
|
-
name: 'announcements',
|
|
100
|
-
type: 'checkbox',
|
|
101
|
-
defaultValue: true,
|
|
102
|
-
label: 'Announcements',
|
|
103
|
-
admin: {
|
|
104
|
-
description: 'Receive important announcements'
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
],
|
|
108
|
-
admin: {
|
|
109
|
-
description: 'Email communication preferences'
|
|
110
|
-
}
|
|
111
|
-
},
|
|
112
|
-
// Source tracking
|
|
113
|
-
{
|
|
114
|
-
name: 'source',
|
|
115
|
-
type: 'text',
|
|
116
|
-
admin: {
|
|
117
|
-
description: 'Where the subscriber signed up from'
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
];
|
|
121
|
-
// Add UTM tracking fields if enabled
|
|
122
|
-
if (pluginConfig.features?.utmTracking?.enabled) {
|
|
123
|
-
const utmFields = pluginConfig.features.utmTracking.fields || [
|
|
124
|
-
'source',
|
|
125
|
-
'medium',
|
|
126
|
-
'campaign',
|
|
127
|
-
'content',
|
|
128
|
-
'term'
|
|
129
|
-
];
|
|
130
|
-
defaultFields.push({
|
|
131
|
-
name: 'utmParameters',
|
|
132
|
-
type: 'group',
|
|
133
|
-
fields: utmFields.map((field)=>({
|
|
134
|
-
name: field,
|
|
135
|
-
type: 'text',
|
|
136
|
-
admin: {
|
|
137
|
-
description: `UTM ${field} parameter`
|
|
138
|
-
}
|
|
139
|
-
})),
|
|
140
|
-
admin: {
|
|
141
|
-
description: 'UTM tracking parameters'
|
|
142
|
-
}
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
// Add signup metadata
|
|
146
|
-
defaultFields.push({
|
|
147
|
-
name: 'signupMetadata',
|
|
148
|
-
type: 'group',
|
|
149
|
-
fields: [
|
|
150
|
-
{
|
|
151
|
-
name: 'ipAddress',
|
|
152
|
-
type: 'text',
|
|
153
|
-
admin: {
|
|
154
|
-
readOnly: true
|
|
155
|
-
}
|
|
156
|
-
},
|
|
157
|
-
{
|
|
158
|
-
name: 'userAgent',
|
|
159
|
-
type: 'text',
|
|
160
|
-
admin: {
|
|
161
|
-
readOnly: true
|
|
162
|
-
}
|
|
163
|
-
},
|
|
164
|
-
{
|
|
165
|
-
name: 'referrer',
|
|
166
|
-
type: 'text',
|
|
167
|
-
admin: {
|
|
168
|
-
readOnly: true
|
|
169
|
-
}
|
|
170
|
-
},
|
|
171
|
-
{
|
|
172
|
-
name: 'signupPage',
|
|
173
|
-
type: 'text',
|
|
174
|
-
admin: {
|
|
175
|
-
readOnly: true
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
],
|
|
179
|
-
admin: {
|
|
180
|
-
description: 'Technical information about signup'
|
|
181
|
-
}
|
|
182
|
-
});
|
|
183
|
-
// Add lead magnet field if enabled
|
|
184
|
-
if (pluginConfig.features?.leadMagnets?.enabled) {
|
|
185
|
-
defaultFields.push({
|
|
186
|
-
name: 'leadMagnet',
|
|
187
|
-
type: 'relationship',
|
|
188
|
-
relationTo: pluginConfig.features.leadMagnets.collection || 'media',
|
|
189
|
-
admin: {
|
|
190
|
-
description: 'Lead magnet downloaded at signup'
|
|
191
|
-
}
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
// Allow field customization
|
|
195
|
-
let fields = defaultFields;
|
|
196
|
-
if (pluginConfig.fields?.overrides) {
|
|
197
|
-
fields = pluginConfig.fields.overrides({
|
|
198
|
-
defaultFields
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
if (pluginConfig.fields?.additional) {
|
|
202
|
-
fields = [
|
|
203
|
-
...fields,
|
|
204
|
-
...pluginConfig.fields.additional
|
|
205
|
-
];
|
|
206
|
-
}
|
|
207
|
-
const subscribersCollection = {
|
|
208
|
-
slug,
|
|
209
|
-
labels: {
|
|
210
|
-
singular: 'Subscriber',
|
|
211
|
-
plural: 'Subscribers'
|
|
212
|
-
},
|
|
213
|
-
admin: {
|
|
214
|
-
useAsTitle: 'email',
|
|
215
|
-
defaultColumns: [
|
|
216
|
-
'email',
|
|
217
|
-
'name',
|
|
218
|
-
'subscriptionStatus',
|
|
219
|
-
'createdAt'
|
|
220
|
-
],
|
|
221
|
-
group: 'Newsletter'
|
|
222
|
-
},
|
|
223
|
-
fields,
|
|
224
|
-
hooks: {
|
|
225
|
-
afterChange: [
|
|
226
|
-
async ({ doc, req, operation, previousDoc })=>{
|
|
227
|
-
// After create logic
|
|
228
|
-
if (operation === 'create') {
|
|
229
|
-
// Add to email service
|
|
230
|
-
const emailService = req.payload.newsletterEmailService;
|
|
231
|
-
if (emailService) {
|
|
232
|
-
try {
|
|
233
|
-
await emailService.addContact(doc);
|
|
234
|
-
} catch {
|
|
235
|
-
// Failed to add contact to email service
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
// Send welcome email if active
|
|
239
|
-
if (doc.subscriptionStatus === 'active' && emailService) {
|
|
240
|
-
try {
|
|
241
|
-
// TODO: Send welcome email
|
|
242
|
-
} catch {
|
|
243
|
-
// Failed to send welcome email
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
// Custom after subscribe hook
|
|
247
|
-
if (pluginConfig.hooks?.afterSubscribe) {
|
|
248
|
-
await pluginConfig.hooks.afterSubscribe({
|
|
249
|
-
doc,
|
|
250
|
-
req
|
|
251
|
-
});
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
// After update logic
|
|
255
|
-
if (operation === 'update' && previousDoc) {
|
|
256
|
-
// Update email service if status changed
|
|
257
|
-
const emailService = req.payload.newsletterEmailService;
|
|
258
|
-
if (doc.subscriptionStatus !== previousDoc.subscriptionStatus && emailService) {
|
|
259
|
-
try {
|
|
260
|
-
await emailService.updateContact(doc);
|
|
261
|
-
} catch {
|
|
262
|
-
// Failed to update contact in email service
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
// Handle unsubscribe
|
|
266
|
-
if (doc.subscriptionStatus === 'unsubscribed' && previousDoc.subscriptionStatus !== 'unsubscribed') {
|
|
267
|
-
// Set unsubscribed timestamp
|
|
268
|
-
doc.unsubscribedAt = new Date().toISOString();
|
|
269
|
-
// Custom after unsubscribe hook
|
|
270
|
-
if (pluginConfig.hooks?.afterUnsubscribe) {
|
|
271
|
-
await pluginConfig.hooks.afterUnsubscribe({
|
|
272
|
-
doc,
|
|
273
|
-
req
|
|
274
|
-
});
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
],
|
|
280
|
-
beforeDelete: [
|
|
281
|
-
async ({ id, req })=>{
|
|
282
|
-
// Remove from email service
|
|
283
|
-
const emailService = req.payload.newsletterEmailService;
|
|
284
|
-
if (emailService) {
|
|
285
|
-
try {
|
|
286
|
-
const doc = await req.payload.findByID({
|
|
287
|
-
collection: slug,
|
|
288
|
-
id
|
|
289
|
-
});
|
|
290
|
-
await emailService.removeContact(doc.email);
|
|
291
|
-
} catch {
|
|
292
|
-
// Failed to remove contact from email service
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
]
|
|
297
|
-
},
|
|
298
|
-
access: {
|
|
299
|
-
create: ()=>true,
|
|
300
|
-
read: adminOrSelf(pluginConfig),
|
|
301
|
-
update: adminOrSelf(pluginConfig),
|
|
302
|
-
delete: adminOnly(pluginConfig)
|
|
303
|
-
},
|
|
304
|
-
timestamps: true
|
|
305
|
-
};
|
|
306
|
-
return subscribersCollection;
|
|
307
|
-
};
|
|
308
|
-
|
|
309
|
-
//# sourceMappingURL=Subscribers.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/collections/Subscribers.ts"],"sourcesContent":["import type { CollectionConfig, Field, CollectionAfterChangeHook, CollectionBeforeDeleteHook } from 'payload'\nimport type { NewsletterPluginConfig } from '../types'\nimport { adminOnly, adminOrSelf } from '../utils/access'\n\nexport const createSubscribersCollection = (\n pluginConfig: NewsletterPluginConfig\n): CollectionConfig => {\n const slug = pluginConfig.subscribersSlug || 'subscribers'\n \n // Default fields for the subscribers collection\n const defaultFields: Field[] = [\n // Core fields\n {\n name: 'email',\n type: 'email',\n required: true,\n unique: true,\n admin: {\n description: 'Subscriber email address',\n },\n },\n {\n name: 'name',\n type: 'text',\n admin: {\n description: 'Subscriber full name',\n },\n },\n {\n name: 'locale',\n type: 'select',\n options: pluginConfig.i18n?.locales?.map(locale => ({\n label: locale.toUpperCase(),\n value: locale,\n })) || [\n { label: 'EN', value: 'en' },\n ],\n defaultValue: pluginConfig.i18n?.defaultLocale || 'en',\n admin: {\n description: 'Preferred language for communications',\n },\n },\n \n // Authentication fields (hidden from admin UI)\n {\n name: 'magicLinkToken',\n type: 'text',\n hidden: true,\n },\n {\n name: 'magicLinkTokenExpiry',\n type: 'date',\n hidden: true,\n },\n \n // Subscription status\n {\n name: 'subscriptionStatus',\n type: 'select',\n options: [\n { label: 'Active', value: 'active' },\n { label: 'Unsubscribed', value: 'unsubscribed' },\n { label: 'Pending', value: 'pending' },\n ],\n defaultValue: 'pending',\n required: true,\n admin: {\n description: 'Current subscription status',\n },\n },\n {\n name: 'unsubscribedAt',\n type: 'date',\n admin: {\n condition: (data) => data?.subscriptionStatus === 'unsubscribed',\n description: 'When the user unsubscribed',\n readOnly: true,\n },\n },\n \n // Email preferences\n {\n name: 'emailPreferences',\n type: 'group',\n fields: [\n {\n name: 'newsletter',\n type: 'checkbox',\n defaultValue: true,\n label: 'Newsletter',\n admin: {\n description: 'Receive regular newsletter updates',\n },\n },\n {\n name: 'announcements',\n type: 'checkbox',\n defaultValue: true,\n label: 'Announcements',\n admin: {\n description: 'Receive important announcements',\n },\n },\n ],\n admin: {\n description: 'Email communication preferences',\n },\n },\n \n // Source tracking\n {\n name: 'source',\n type: 'text',\n admin: {\n description: 'Where the subscriber signed up from',\n },\n },\n ]\n\n // Add UTM tracking fields if enabled\n if (pluginConfig.features?.utmTracking?.enabled) {\n const utmFields = pluginConfig.features.utmTracking.fields || [\n 'source',\n 'medium',\n 'campaign',\n 'content',\n 'term',\n ]\n \n defaultFields.push({\n name: 'utmParameters',\n type: 'group',\n fields: utmFields.map(field => ({\n name: field,\n type: 'text',\n admin: {\n description: `UTM ${field} parameter`,\n },\n })),\n admin: {\n description: 'UTM tracking parameters',\n },\n })\n }\n\n // Add signup metadata\n defaultFields.push({\n name: 'signupMetadata',\n type: 'group',\n fields: [\n {\n name: 'ipAddress',\n type: 'text',\n admin: {\n readOnly: true,\n },\n },\n {\n name: 'userAgent',\n type: 'text',\n admin: {\n readOnly: true,\n },\n },\n {\n name: 'referrer',\n type: 'text',\n admin: {\n readOnly: true,\n },\n },\n {\n name: 'signupPage',\n type: 'text',\n admin: {\n readOnly: true,\n },\n },\n ],\n admin: {\n description: 'Technical information about signup',\n },\n })\n\n // Add lead magnet field if enabled\n if (pluginConfig.features?.leadMagnets?.enabled) {\n defaultFields.push({\n name: 'leadMagnet',\n type: 'relationship',\n relationTo: pluginConfig.features.leadMagnets.collection || 'media',\n admin: {\n description: 'Lead magnet downloaded at signup',\n },\n })\n }\n\n // Allow field customization\n let fields = defaultFields\n if (pluginConfig.fields?.overrides) {\n fields = pluginConfig.fields.overrides({ defaultFields })\n }\n if (pluginConfig.fields?.additional) {\n fields = [...fields, ...pluginConfig.fields.additional]\n }\n\n const subscribersCollection: CollectionConfig = {\n slug,\n labels: {\n singular: 'Subscriber',\n plural: 'Subscribers',\n },\n admin: {\n useAsTitle: 'email',\n defaultColumns: ['email', 'name', 'subscriptionStatus', 'createdAt'],\n group: 'Newsletter',\n },\n fields,\n hooks: {\n afterChange: [\n async ({ doc, req, operation, previousDoc }) => {\n // After create logic\n if (operation === 'create') {\n // Add to email service\n const emailService = (req.payload as any).newsletterEmailService\n if (emailService) {\n try {\n await emailService.addContact(doc)\n } catch {\n // Failed to add contact to email service\n }\n }\n\n // Send welcome email if active\n if (doc.subscriptionStatus === 'active' && emailService) {\n try {\n // TODO: Send welcome email\n } catch {\n // Failed to send welcome email\n }\n }\n\n // Custom after subscribe hook\n if (pluginConfig.hooks?.afterSubscribe) {\n await pluginConfig.hooks.afterSubscribe({ doc, req })\n }\n }\n \n // After update logic\n if (operation === 'update' && previousDoc) {\n // Update email service if status changed\n const emailService = (req.payload as any).newsletterEmailService\n if (\n doc.subscriptionStatus !== previousDoc.subscriptionStatus &&\n emailService\n ) {\n try {\n await emailService.updateContact(doc)\n } catch {\n // Failed to update contact in email service\n }\n }\n\n // Handle unsubscribe\n if (\n doc.subscriptionStatus === 'unsubscribed' &&\n previousDoc.subscriptionStatus !== 'unsubscribed'\n ) {\n // Set unsubscribed timestamp\n doc.unsubscribedAt = new Date().toISOString()\n \n // Custom after unsubscribe hook\n if (pluginConfig.hooks?.afterUnsubscribe) {\n await pluginConfig.hooks.afterUnsubscribe({ doc, req })\n }\n }\n }\n },\n ] as CollectionAfterChangeHook[],\n beforeDelete: [\n async ({ id, req }) => {\n // Remove from email service\n const emailService = (req.payload as any).newsletterEmailService\n if (emailService) {\n try {\n const doc = await req.payload.findByID({\n collection: slug,\n id,\n })\n await emailService.removeContact(doc.email)\n } catch {\n // Failed to remove contact from email service\n }\n }\n },\n ] as CollectionBeforeDeleteHook[],\n },\n access: {\n create: () => true, // Public can subscribe\n read: adminOrSelf(pluginConfig),\n update: adminOrSelf(pluginConfig),\n delete: adminOnly(pluginConfig),\n },\n timestamps: true,\n }\n\n return subscribersCollection\n}"],"names":["adminOnly","adminOrSelf","createSubscribersCollection","pluginConfig","slug","subscribersSlug","defaultFields","name","type","required","unique","admin","description","options","i18n","locales","map","locale","label","toUpperCase","value","defaultValue","defaultLocale","hidden","condition","data","subscriptionStatus","readOnly","fields","features","utmTracking","enabled","utmFields","push","field","leadMagnets","relationTo","collection","overrides","additional","subscribersCollection","labels","singular","plural","useAsTitle","defaultColumns","group","hooks","afterChange","doc","req","operation","previousDoc","emailService","payload","newsletterEmailService","addContact","afterSubscribe","updateContact","unsubscribedAt","Date","toISOString","afterUnsubscribe","beforeDelete","id","findByID","removeContact","email","access","create","read","update","delete","timestamps"],"mappings":"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"}
|
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
-
import React, { useState, useEffect } from 'react';
|
|
4
|
-
const defaultStyles = {
|
|
5
|
-
container: {
|
|
6
|
-
maxWidth: '400px',
|
|
7
|
-
margin: '4rem auto',
|
|
8
|
-
padding: '2rem',
|
|
9
|
-
textAlign: 'center'
|
|
10
|
-
},
|
|
11
|
-
heading: {
|
|
12
|
-
fontSize: '1.5rem',
|
|
13
|
-
fontWeight: '600',
|
|
14
|
-
marginBottom: '1rem',
|
|
15
|
-
color: '#111827'
|
|
16
|
-
},
|
|
17
|
-
message: {
|
|
18
|
-
fontSize: '1rem',
|
|
19
|
-
color: '#6b7280',
|
|
20
|
-
marginBottom: '1.5rem'
|
|
21
|
-
},
|
|
22
|
-
error: {
|
|
23
|
-
fontSize: '1rem',
|
|
24
|
-
color: '#ef4444',
|
|
25
|
-
marginBottom: '1.5rem'
|
|
26
|
-
},
|
|
27
|
-
button: {
|
|
28
|
-
padding: '0.75rem 1.5rem',
|
|
29
|
-
fontSize: '1rem',
|
|
30
|
-
fontWeight: '500',
|
|
31
|
-
color: '#ffffff',
|
|
32
|
-
backgroundColor: '#3b82f6',
|
|
33
|
-
border: 'none',
|
|
34
|
-
borderRadius: '0.375rem',
|
|
35
|
-
cursor: 'pointer',
|
|
36
|
-
transition: 'background-color 0.2s'
|
|
37
|
-
}
|
|
38
|
-
};
|
|
39
|
-
export const MagicLinkVerify = ({ token: propToken, onSuccess, onError, apiEndpoint = '/api/newsletter/verify-magic-link', className, styles: customStyles = {}, labels = {
|
|
40
|
-
verifying: 'Verifying your magic link...',
|
|
41
|
-
success: 'Successfully verified! Redirecting...',
|
|
42
|
-
error: 'Failed to verify magic link',
|
|
43
|
-
expired: 'This magic link has expired. Please request a new one.',
|
|
44
|
-
invalid: 'This magic link is invalid. Please request a new one.',
|
|
45
|
-
redirecting: 'Redirecting to your preferences...',
|
|
46
|
-
tryAgain: 'Try Again'
|
|
47
|
-
} })=>{
|
|
48
|
-
const [status, setStatus] = useState('verifying');
|
|
49
|
-
const [error, setError] = useState(null);
|
|
50
|
-
const [_sessionToken, setSessionToken] = useState(null);
|
|
51
|
-
const styles = {
|
|
52
|
-
container: {
|
|
53
|
-
...defaultStyles.container,
|
|
54
|
-
...customStyles.container
|
|
55
|
-
},
|
|
56
|
-
heading: {
|
|
57
|
-
...defaultStyles.heading,
|
|
58
|
-
...customStyles.heading
|
|
59
|
-
},
|
|
60
|
-
message: {
|
|
61
|
-
...defaultStyles.message,
|
|
62
|
-
...customStyles.message
|
|
63
|
-
},
|
|
64
|
-
error: {
|
|
65
|
-
...defaultStyles.error,
|
|
66
|
-
...customStyles.error
|
|
67
|
-
},
|
|
68
|
-
button: {
|
|
69
|
-
...defaultStyles.button,
|
|
70
|
-
...customStyles.button
|
|
71
|
-
}
|
|
72
|
-
};
|
|
73
|
-
useEffect(()=>{
|
|
74
|
-
// Get token from props or URL
|
|
75
|
-
const token = propToken || new URLSearchParams(window.location.search).get('token');
|
|
76
|
-
if (token) {
|
|
77
|
-
verifyToken(token);
|
|
78
|
-
} else {
|
|
79
|
-
setStatus('error');
|
|
80
|
-
setError(labels.invalid || 'Invalid magic link');
|
|
81
|
-
}
|
|
82
|
-
}, [
|
|
83
|
-
propToken
|
|
84
|
-
]);
|
|
85
|
-
const verifyToken = async (token)=>{
|
|
86
|
-
try {
|
|
87
|
-
const response = await fetch(apiEndpoint, {
|
|
88
|
-
method: 'POST',
|
|
89
|
-
headers: {
|
|
90
|
-
'Content-Type': 'application/json'
|
|
91
|
-
},
|
|
92
|
-
body: JSON.stringify({
|
|
93
|
-
token
|
|
94
|
-
})
|
|
95
|
-
});
|
|
96
|
-
const data = await response.json();
|
|
97
|
-
if (!response.ok) {
|
|
98
|
-
if (data.error?.includes('expired')) {
|
|
99
|
-
throw new Error(labels.expired);
|
|
100
|
-
}
|
|
101
|
-
throw new Error(data.error || labels.error);
|
|
102
|
-
}
|
|
103
|
-
setStatus('success');
|
|
104
|
-
setSessionToken(data.sessionToken);
|
|
105
|
-
// Store session token
|
|
106
|
-
if (typeof window !== 'undefined' && data.sessionToken) {
|
|
107
|
-
localStorage.setItem('newsletter_session', data.sessionToken);
|
|
108
|
-
}
|
|
109
|
-
if (onSuccess) {
|
|
110
|
-
onSuccess(data.sessionToken, data.subscriber);
|
|
111
|
-
}
|
|
112
|
-
} catch (err) {
|
|
113
|
-
setStatus('error');
|
|
114
|
-
const errorMessage = err instanceof Error ? err.message : labels.error || 'Verification failed';
|
|
115
|
-
setError(errorMessage);
|
|
116
|
-
if (onError) {
|
|
117
|
-
onError(err instanceof Error ? err : new Error(errorMessage));
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
};
|
|
121
|
-
const handleTryAgain = ()=>{
|
|
122
|
-
window.location.href = '/';
|
|
123
|
-
};
|
|
124
|
-
return /*#__PURE__*/ _jsxs("div", {
|
|
125
|
-
className: className,
|
|
126
|
-
style: styles.container,
|
|
127
|
-
children: [
|
|
128
|
-
status === 'verifying' && /*#__PURE__*/ _jsxs(_Fragment, {
|
|
129
|
-
children: [
|
|
130
|
-
/*#__PURE__*/ _jsx("h2", {
|
|
131
|
-
style: styles.heading,
|
|
132
|
-
children: "Verifying"
|
|
133
|
-
}),
|
|
134
|
-
/*#__PURE__*/ _jsx("p", {
|
|
135
|
-
style: styles.message,
|
|
136
|
-
children: labels.verifying
|
|
137
|
-
})
|
|
138
|
-
]
|
|
139
|
-
}),
|
|
140
|
-
status === 'success' && /*#__PURE__*/ _jsxs(_Fragment, {
|
|
141
|
-
children: [
|
|
142
|
-
/*#__PURE__*/ _jsx("h2", {
|
|
143
|
-
style: styles.heading,
|
|
144
|
-
children: "Success!"
|
|
145
|
-
}),
|
|
146
|
-
/*#__PURE__*/ _jsx("p", {
|
|
147
|
-
style: styles.message,
|
|
148
|
-
children: labels.success
|
|
149
|
-
})
|
|
150
|
-
]
|
|
151
|
-
}),
|
|
152
|
-
status === 'error' && /*#__PURE__*/ _jsxs(_Fragment, {
|
|
153
|
-
children: [
|
|
154
|
-
/*#__PURE__*/ _jsx("h2", {
|
|
155
|
-
style: styles.heading,
|
|
156
|
-
children: "Verification Failed"
|
|
157
|
-
}),
|
|
158
|
-
/*#__PURE__*/ _jsx("p", {
|
|
159
|
-
style: styles.error,
|
|
160
|
-
children: error
|
|
161
|
-
}),
|
|
162
|
-
/*#__PURE__*/ _jsx("button", {
|
|
163
|
-
onClick: handleTryAgain,
|
|
164
|
-
style: styles.button,
|
|
165
|
-
children: labels.tryAgain
|
|
166
|
-
})
|
|
167
|
-
]
|
|
168
|
-
})
|
|
169
|
-
]
|
|
170
|
-
});
|
|
171
|
-
};
|
|
172
|
-
// Factory function for creating custom magic link verify components
|
|
173
|
-
export function createMagicLinkVerify(defaultProps) {
|
|
174
|
-
return (props)=>/*#__PURE__*/ _jsx(MagicLinkVerify, {
|
|
175
|
-
...defaultProps,
|
|
176
|
-
...props
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
//# sourceMappingURL=MagicLinkVerify.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/MagicLinkVerify.tsx"],"sourcesContent":["'use client'\n\nimport React, { useState, useEffect } from 'react'\n\nexport interface MagicLinkVerifyProps {\n token?: string\n onSuccess?: (sessionToken: string, subscriber: any) => void\n onError?: (error: Error) => void\n apiEndpoint?: string\n className?: string\n styles?: {\n container?: React.CSSProperties\n heading?: React.CSSProperties\n message?: React.CSSProperties\n error?: React.CSSProperties\n button?: React.CSSProperties\n }\n labels?: {\n verifying?: string\n success?: string\n error?: string\n expired?: string\n invalid?: string\n redirecting?: string\n tryAgain?: string\n }\n}\n\nconst defaultStyles = {\n container: {\n maxWidth: '400px',\n margin: '4rem auto',\n padding: '2rem',\n textAlign: 'center' as const,\n },\n heading: {\n fontSize: '1.5rem',\n fontWeight: '600',\n marginBottom: '1rem',\n color: '#111827',\n },\n message: {\n fontSize: '1rem',\n color: '#6b7280',\n marginBottom: '1.5rem',\n },\n error: {\n fontSize: '1rem',\n color: '#ef4444',\n marginBottom: '1.5rem',\n },\n button: {\n padding: '0.75rem 1.5rem',\n fontSize: '1rem',\n fontWeight: '500',\n color: '#ffffff',\n backgroundColor: '#3b82f6',\n border: 'none',\n borderRadius: '0.375rem',\n cursor: 'pointer',\n transition: 'background-color 0.2s',\n },\n}\n\nexport const MagicLinkVerify: React.FC<MagicLinkVerifyProps> = ({\n token: propToken,\n onSuccess,\n onError,\n apiEndpoint = '/api/newsletter/verify-magic-link',\n className,\n styles: customStyles = {},\n labels = {\n verifying: 'Verifying your magic link...',\n success: 'Successfully verified! Redirecting...',\n error: 'Failed to verify magic link',\n expired: 'This magic link has expired. Please request a new one.',\n invalid: 'This magic link is invalid. Please request a new one.',\n redirecting: 'Redirecting to your preferences...',\n tryAgain: 'Try Again',\n },\n}) => {\n const [status, setStatus] = useState<'verifying' | 'success' | 'error'>('verifying')\n const [error, setError] = useState<string | null>(null)\n const [_sessionToken, setSessionToken] = useState<string | null>(null)\n\n const styles = {\n container: { ...defaultStyles.container, ...customStyles.container },\n heading: { ...defaultStyles.heading, ...customStyles.heading },\n message: { ...defaultStyles.message, ...customStyles.message },\n error: { ...defaultStyles.error, ...customStyles.error },\n button: { ...defaultStyles.button, ...customStyles.button },\n }\n\n useEffect(() => {\n // Get token from props or URL\n const token = propToken || new URLSearchParams(window.location.search).get('token')\n \n if (token) {\n verifyToken(token)\n } else {\n setStatus('error')\n setError(labels.invalid || 'Invalid magic link')\n }\n }, [propToken])\n\n const verifyToken = async (token: string) => {\n try {\n const response = await fetch(apiEndpoint, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ token }),\n })\n\n const data = await response.json()\n\n if (!response.ok) {\n if (data.error?.includes('expired')) {\n throw new Error(labels.expired)\n }\n throw new Error(data.error || labels.error)\n }\n\n setStatus('success')\n setSessionToken(data.sessionToken)\n\n // Store session token\n if (typeof window !== 'undefined' && data.sessionToken) {\n localStorage.setItem('newsletter_session', data.sessionToken)\n }\n\n if (onSuccess) {\n onSuccess(data.sessionToken, data.subscriber)\n }\n } catch (err) {\n setStatus('error')\n const errorMessage = err instanceof Error ? err.message : (labels.error || 'Verification failed')\n setError(errorMessage)\n if (onError) {\n onError(err instanceof Error ? err : new Error(errorMessage))\n }\n }\n }\n\n const handleTryAgain = () => {\n window.location.href = '/'\n }\n\n return (\n <div className={className} style={styles.container}>\n {status === 'verifying' && (\n <>\n <h2 style={styles.heading}>Verifying</h2>\n <p style={styles.message}>{labels.verifying}</p>\n </>\n )}\n\n {status === 'success' && (\n <>\n <h2 style={styles.heading}>Success!</h2>\n <p style={styles.message}>{labels.success}</p>\n </>\n )}\n\n {status === 'error' && (\n <>\n <h2 style={styles.heading}>Verification Failed</h2>\n <p style={styles.error}>{error}</p>\n <button onClick={handleTryAgain} style={styles.button}>\n {labels.tryAgain}\n </button>\n </>\n )}\n </div>\n )\n}\n\n// Factory function for creating custom magic link verify components\nexport function createMagicLinkVerify(\n defaultProps: Partial<MagicLinkVerifyProps>\n): React.FC<MagicLinkVerifyProps> {\n return (props: MagicLinkVerifyProps) => (\n <MagicLinkVerify {...defaultProps} {...props} />\n )\n}"],"names":["React","useState","useEffect","defaultStyles","container","maxWidth","margin","padding","textAlign","heading","fontSize","fontWeight","marginBottom","color","message","error","button","backgroundColor","border","borderRadius","cursor","transition","MagicLinkVerify","token","propToken","onSuccess","onError","apiEndpoint","className","styles","customStyles","labels","verifying","success","expired","invalid","redirecting","tryAgain","status","setStatus","setError","_sessionToken","setSessionToken","URLSearchParams","window","location","search","get","verifyToken","response","fetch","method","headers","body","JSON","stringify","data","json","ok","includes","Error","sessionToken","localStorage","setItem","subscriber","err","errorMessage","handleTryAgain","href","div","style","h2","p","onClick","createMagicLinkVerify","defaultProps","props"],"mappings":"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"}
|