payload-plugin-newsletter 0.3.2 → 0.4.5

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 (180) hide show
  1. package/CHANGELOG.md +44 -1
  2. package/CLAUDE.md +31 -19
  3. package/dist/client.cjs +899 -0
  4. package/dist/client.cjs.map +1 -0
  5. package/dist/client.d.cts +52 -0
  6. package/dist/client.d.ts +52 -0
  7. package/dist/client.js +867 -0
  8. package/dist/client.js.map +1 -0
  9. package/dist/components.cjs +899 -0
  10. package/dist/components.cjs.map +1 -0
  11. package/dist/components.d.cts +4 -0
  12. package/dist/components.d.ts +4 -0
  13. package/dist/components.js +867 -0
  14. package/dist/components.js.map +1 -0
  15. package/dist/index.cjs +2004 -0
  16. package/dist/index.cjs.map +1 -0
  17. package/dist/index.d.cts +11 -0
  18. package/dist/index.d.ts +6 -5
  19. package/dist/index.js +1967 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/types.cjs +19 -0
  22. package/dist/types.cjs.map +1 -0
  23. package/dist/{types/index.d.ts → types.d.cts} +19 -17
  24. package/dist/types.d.ts +350 -0
  25. package/dist/types.js +1 -0
  26. package/dist/types.js.map +1 -0
  27. package/package.json +48 -25
  28. package/dist/.tsbuildinfo +0 -1
  29. package/dist/collections/NewsletterSettings.d.ts +0 -4
  30. package/dist/collections/NewsletterSettings.d.ts.map +0 -1
  31. package/dist/collections/Subscribers.d.ts +0 -4
  32. package/dist/collections/Subscribers.d.ts.map +0 -1
  33. package/dist/components/MagicLinkVerify.d.ts +0 -27
  34. package/dist/components/MagicLinkVerify.d.ts.map +0 -1
  35. package/dist/components/NewsletterForm.d.ts +0 -5
  36. package/dist/components/NewsletterForm.d.ts.map +0 -1
  37. package/dist/components/PreferencesForm.d.ts +0 -5
  38. package/dist/components/PreferencesForm.d.ts.map +0 -1
  39. package/dist/components/index.d.ts +0 -5
  40. package/dist/components/index.d.ts.map +0 -1
  41. package/dist/endpoints/index.d.ts +0 -4
  42. package/dist/endpoints/index.d.ts.map +0 -1
  43. package/dist/endpoints/preferences.d.ts +0 -5
  44. package/dist/endpoints/preferences.d.ts.map +0 -1
  45. package/dist/endpoints/subscribe.d.ts +0 -4
  46. package/dist/endpoints/subscribe.d.ts.map +0 -1
  47. package/dist/endpoints/unsubscribe.d.ts +0 -4
  48. package/dist/endpoints/unsubscribe.d.ts.map +0 -1
  49. package/dist/endpoints/verify-magic-link.d.ts +0 -4
  50. package/dist/endpoints/verify-magic-link.d.ts.map +0 -1
  51. package/dist/exports/client.d.ts +0 -6
  52. package/dist/exports/client.d.ts.map +0 -1
  53. package/dist/exports/components.d.ts +0 -2
  54. package/dist/exports/components.d.ts.map +0 -1
  55. package/dist/exports/types.d.ts +0 -2
  56. package/dist/exports/types.d.ts.map +0 -1
  57. package/dist/fields/newsletterScheduling.d.ts +0 -4
  58. package/dist/fields/newsletterScheduling.d.ts.map +0 -1
  59. package/dist/hooks/useNewsletterAuth.d.ts +0 -16
  60. package/dist/hooks/useNewsletterAuth.d.ts.map +0 -1
  61. package/dist/index.d.ts.map +0 -1
  62. package/dist/providers/broadcast.d.ts +0 -19
  63. package/dist/providers/broadcast.d.ts.map +0 -1
  64. package/dist/providers/index.d.ts +0 -23
  65. package/dist/providers/index.d.ts.map +0 -1
  66. package/dist/providers/resend.d.ts +0 -20
  67. package/dist/providers/resend.d.ts.map +0 -1
  68. package/dist/providers/types.d.ts +0 -46
  69. package/dist/providers/types.d.ts.map +0 -1
  70. package/dist/src/__tests__/fixtures/newsletter-settings.js +0 -41
  71. package/dist/src/__tests__/fixtures/newsletter-settings.js.map +0 -1
  72. package/dist/src/__tests__/fixtures/subscribers.js +0 -70
  73. package/dist/src/__tests__/fixtures/subscribers.js.map +0 -1
  74. package/dist/src/__tests__/integration/collections/subscriber-hooks.test.js +0 -356
  75. package/dist/src/__tests__/integration/collections/subscriber-hooks.test.js.map +0 -1
  76. package/dist/src/__tests__/integration/endpoints/preferences.test.js +0 -266
  77. package/dist/src/__tests__/integration/endpoints/preferences.test.js.map +0 -1
  78. package/dist/src/__tests__/integration/endpoints/subscribe.test.js +0 -280
  79. package/dist/src/__tests__/integration/endpoints/subscribe.test.js.map +0 -1
  80. package/dist/src/__tests__/integration/endpoints/unsubscribe.test.js +0 -187
  81. package/dist/src/__tests__/integration/endpoints/unsubscribe.test.js.map +0 -1
  82. package/dist/src/__tests__/integration/endpoints/verify-magic-link.test.js +0 -188
  83. package/dist/src/__tests__/integration/endpoints/verify-magic-link.test.js.map +0 -1
  84. package/dist/src/__tests__/mocks/email-providers.js +0 -153
  85. package/dist/src/__tests__/mocks/email-providers.js.map +0 -1
  86. package/dist/src/__tests__/mocks/payload.js +0 -244
  87. package/dist/src/__tests__/mocks/payload.js.map +0 -1
  88. package/dist/src/__tests__/security/csrf-protection.test.js +0 -309
  89. package/dist/src/__tests__/security/csrf-protection.test.js.map +0 -1
  90. package/dist/src/__tests__/security/settings-access.test.js +0 -204
  91. package/dist/src/__tests__/security/settings-access.test.js.map +0 -1
  92. package/dist/src/__tests__/security/subscriber-access.test.js +0 -210
  93. package/dist/src/__tests__/security/subscriber-access.test.js.map +0 -1
  94. package/dist/src/__tests__/security/xss-prevention.test.js +0 -305
  95. package/dist/src/__tests__/security/xss-prevention.test.js.map +0 -1
  96. package/dist/src/__tests__/setup/integration.setup.js +0 -38
  97. package/dist/src/__tests__/setup/integration.setup.js.map +0 -1
  98. package/dist/src/__tests__/setup/unit.setup.js +0 -41
  99. package/dist/src/__tests__/setup/unit.setup.js.map +0 -1
  100. package/dist/src/__tests__/unit/utils/access.test.js +0 -116
  101. package/dist/src/__tests__/unit/utils/access.test.js.map +0 -1
  102. package/dist/src/__tests__/unit/utils/jwt.test.js +0 -238
  103. package/dist/src/__tests__/unit/utils/jwt.test.js.map +0 -1
  104. package/dist/src/collections/NewsletterSettings.js +0 -390
  105. package/dist/src/collections/NewsletterSettings.js.map +0 -1
  106. package/dist/src/collections/Subscribers.js +0 -309
  107. package/dist/src/collections/Subscribers.js.map +0 -1
  108. package/dist/src/components/MagicLinkVerify.js +0 -180
  109. package/dist/src/components/MagicLinkVerify.js.map +0 -1
  110. package/dist/src/components/NewsletterForm.js +0 -326
  111. package/dist/src/components/NewsletterForm.js.map +0 -1
  112. package/dist/src/components/PreferencesForm.js +0 -524
  113. package/dist/src/components/PreferencesForm.js.map +0 -1
  114. package/dist/src/components/index.js +0 -5
  115. package/dist/src/components/index.js.map +0 -1
  116. package/dist/src/endpoints/index.js +0 -17
  117. package/dist/src/endpoints/index.js.map +0 -1
  118. package/dist/src/endpoints/preferences.js +0 -136
  119. package/dist/src/endpoints/preferences.js.map +0 -1
  120. package/dist/src/endpoints/subscribe.js +0 -151
  121. package/dist/src/endpoints/subscribe.js.map +0 -1
  122. package/dist/src/endpoints/unsubscribe.js +0 -105
  123. package/dist/src/endpoints/unsubscribe.js.map +0 -1
  124. package/dist/src/endpoints/verify-magic-link.js +0 -103
  125. package/dist/src/endpoints/verify-magic-link.js.map +0 -1
  126. package/dist/src/exports/client.js +0 -7
  127. package/dist/src/exports/client.js.map +0 -1
  128. package/dist/src/exports/components.js +0 -6
  129. package/dist/src/exports/components.js.map +0 -1
  130. package/dist/src/exports/types.js +0 -3
  131. package/dist/src/exports/types.js.map +0 -1
  132. package/dist/src/fields/newsletterScheduling.js +0 -195
  133. package/dist/src/fields/newsletterScheduling.js.map +0 -1
  134. package/dist/src/hooks/useNewsletterAuth.js +0 -112
  135. package/dist/src/hooks/useNewsletterAuth.js.map +0 -1
  136. package/dist/src/index.js +0 -130
  137. package/dist/src/index.js.map +0 -1
  138. package/dist/src/providers/broadcast.js +0 -158
  139. package/dist/src/providers/broadcast.js.map +0 -1
  140. package/dist/src/providers/index.js +0 -63
  141. package/dist/src/providers/index.js.map +0 -1
  142. package/dist/src/providers/resend.js +0 -122
  143. package/dist/src/providers/resend.js.map +0 -1
  144. package/dist/src/providers/types.js +0 -12
  145. package/dist/src/providers/types.js.map +0 -1
  146. package/dist/src/templates/BaseTemplate.js +0 -105
  147. package/dist/src/templates/BaseTemplate.js.map +0 -1
  148. package/dist/src/templates/MagicLinkTemplate.js +0 -178
  149. package/dist/src/templates/MagicLinkTemplate.js.map +0 -1
  150. package/dist/src/templates/NewsletterTemplate.js +0 -150
  151. package/dist/src/templates/NewsletterTemplate.js.map +0 -1
  152. package/dist/src/templates/WelcomeTemplate.js +0 -192
  153. package/dist/src/templates/WelcomeTemplate.js.map +0 -1
  154. package/dist/src/templates/index.js +0 -6
  155. package/dist/src/templates/index.js.map +0 -1
  156. package/dist/src/types/index.js +0 -3
  157. package/dist/src/types/index.js.map +0 -1
  158. package/dist/src/utils/access.js +0 -80
  159. package/dist/src/utils/access.js.map +0 -1
  160. package/dist/src/utils/jwt.js +0 -91
  161. package/dist/src/utils/jwt.js.map +0 -1
  162. package/dist/src/utils/validation.js +0 -74
  163. package/dist/src/utils/validation.js.map +0 -1
  164. package/dist/templates/BaseTemplate.d.ts +0 -45
  165. package/dist/templates/BaseTemplate.d.ts.map +0 -1
  166. package/dist/templates/MagicLinkTemplate.d.ts +0 -67
  167. package/dist/templates/MagicLinkTemplate.d.ts.map +0 -1
  168. package/dist/templates/NewsletterTemplate.d.ts +0 -112
  169. package/dist/templates/NewsletterTemplate.d.ts.map +0 -1
  170. package/dist/templates/WelcomeTemplate.d.ts +0 -55
  171. package/dist/templates/WelcomeTemplate.d.ts.map +0 -1
  172. package/dist/templates/index.d.ts +0 -7
  173. package/dist/templates/index.d.ts.map +0 -1
  174. package/dist/types/index.d.ts.map +0 -1
  175. package/dist/utils/access.d.ts +0 -15
  176. package/dist/utils/access.d.ts.map +0 -1
  177. package/dist/utils/jwt.d.ts +0 -32
  178. package/dist/utils/jwt.d.ts.map +0 -1
  179. package/dist/utils/validation.d.ts +0 -25
  180. package/dist/utils/validation.d.ts.map +0 -1
@@ -1,136 +0,0 @@
1
- import { verifySessionToken } from '../utils/jwt';
2
- export const createPreferencesEndpoint = (config)=>{
3
- return {
4
- path: '/newsletter/preferences',
5
- method: 'get',
6
- handler: async (req, res)=>{
7
- try {
8
- // Get token from Authorization header
9
- const authHeader = req.headers.authorization;
10
- if (!authHeader || !authHeader.startsWith('Bearer ')) {
11
- return res.status(401).json({
12
- success: false,
13
- error: 'Authorization required'
14
- });
15
- }
16
- const token = authHeader.substring(7);
17
- // Verify session token
18
- let payload;
19
- try {
20
- payload = verifySessionToken(token);
21
- } catch (error) {
22
- return res.status(401).json({
23
- success: false,
24
- error: error instanceof Error ? error.message : 'Invalid token'
25
- });
26
- }
27
- // Get subscriber - use synthetic user to ensure access control
28
- const subscriber = await req.payload.findByID({
29
- collection: config.subscribersSlug || 'subscribers',
30
- id: payload.subscriberId,
31
- overrideAccess: false,
32
- user: {
33
- collection: 'subscribers',
34
- id: payload.subscriberId,
35
- email: payload.email
36
- }
37
- });
38
- if (!subscriber) {
39
- return res.status(404).json({
40
- success: false,
41
- error: 'Subscriber not found'
42
- });
43
- }
44
- res.json({
45
- success: true,
46
- subscriber: {
47
- id: subscriber.id,
48
- email: subscriber.email,
49
- name: subscriber.name,
50
- locale: subscriber.locale,
51
- emailPreferences: subscriber.emailPreferences,
52
- subscriptionStatus: subscriber.subscriptionStatus
53
- }
54
- });
55
- } catch (error) {
56
- console.error('Get preferences error:', error);
57
- res.status(500).json({
58
- success: false,
59
- error: 'Failed to get preferences'
60
- });
61
- }
62
- }
63
- };
64
- };
65
- export const createUpdatePreferencesEndpoint = (config)=>{
66
- return {
67
- path: '/newsletter/preferences',
68
- method: 'post',
69
- handler: async (req, res)=>{
70
- try {
71
- // Get token from Authorization header
72
- const authHeader = req.headers.authorization;
73
- if (!authHeader || !authHeader.startsWith('Bearer ')) {
74
- return res.status(401).json({
75
- success: false,
76
- error: 'Authorization required'
77
- });
78
- }
79
- const token = authHeader.substring(7);
80
- // Verify session token
81
- let payload;
82
- try {
83
- payload = verifySessionToken(token);
84
- } catch (error) {
85
- return res.status(401).json({
86
- success: false,
87
- error: error instanceof Error ? error.message : 'Invalid token'
88
- });
89
- }
90
- const { name, locale, emailPreferences } = req.body;
91
- // Prepare update data
92
- const updateData = {};
93
- if (name !== undefined) {
94
- updateData.name = name;
95
- }
96
- if (locale !== undefined) {
97
- updateData.locale = locale;
98
- }
99
- if (emailPreferences !== undefined) {
100
- updateData.emailPreferences = emailPreferences;
101
- }
102
- // Update subscriber - use synthetic user to ensure only updating own data
103
- const subscriber = await req.payload.update({
104
- collection: config.subscribersSlug || 'subscribers',
105
- id: payload.subscriberId,
106
- data: updateData,
107
- overrideAccess: false,
108
- user: {
109
- collection: 'subscribers',
110
- id: payload.subscriberId,
111
- email: payload.email
112
- }
113
- });
114
- res.json({
115
- success: true,
116
- subscriber: {
117
- id: subscriber.id,
118
- email: subscriber.email,
119
- name: subscriber.name,
120
- locale: subscriber.locale,
121
- emailPreferences: subscriber.emailPreferences,
122
- subscriptionStatus: subscriber.subscriptionStatus
123
- }
124
- });
125
- } catch (error) {
126
- console.error('Update preferences error:', error);
127
- res.status(500).json({
128
- success: false,
129
- error: 'Failed to update preferences'
130
- });
131
- }
132
- }
133
- };
134
- };
135
-
136
- //# sourceMappingURL=preferences.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../src/endpoints/preferences.ts"],"sourcesContent":["import type { Endpoint, PayloadHandler } from 'payload'\nimport type { NewsletterPluginConfig } from '../types'\nimport { verifySessionToken } from '../utils/jwt'\n\nexport const createPreferencesEndpoint = (\n config: NewsletterPluginConfig\n): Endpoint => {\n return {\n path: '/newsletter/preferences',\n method: 'get',\n handler: (async (req: any, res: any) => {\n try {\n // Get token from Authorization header\n const authHeader = req.headers.authorization\n if (!authHeader || !authHeader.startsWith('Bearer ')) {\n return res.status(401).json({\n success: false,\n error: 'Authorization required',\n })\n }\n\n const token = authHeader.substring(7)\n\n // Verify session token\n let payload\n try {\n payload = verifySessionToken(token)\n } catch (error: unknown) {\n return res.status(401).json({\n success: false,\n error: error instanceof Error ? error.message : 'Invalid token',\n })\n }\n\n // Get subscriber - use synthetic user to ensure access control\n const subscriber = await req.payload.findByID({\n collection: config.subscribersSlug || 'subscribers',\n id: payload.subscriberId,\n overrideAccess: false,\n user: {\n collection: 'subscribers',\n id: payload.subscriberId,\n email: payload.email,\n },\n })\n\n if (!subscriber) {\n return res.status(404).json({\n success: false,\n error: 'Subscriber not found',\n })\n }\n\n res.json({\n success: true,\n subscriber: {\n id: subscriber.id,\n email: subscriber.email,\n name: subscriber.name,\n locale: subscriber.locale,\n emailPreferences: subscriber.emailPreferences,\n subscriptionStatus: subscriber.subscriptionStatus,\n },\n })\n } catch (error: unknown) {\n console.error('Get preferences error:', error)\n res.status(500).json({\n success: false,\n error: 'Failed to get preferences',\n })\n }\n }) as PayloadHandler,\n }\n}\n\nexport const createUpdatePreferencesEndpoint = (\n config: NewsletterPluginConfig\n): Endpoint => {\n return {\n path: '/newsletter/preferences',\n method: 'post',\n handler: (async (req: any, res: any) => {\n try {\n // Get token from Authorization header\n const authHeader = req.headers.authorization\n if (!authHeader || !authHeader.startsWith('Bearer ')) {\n return res.status(401).json({\n success: false,\n error: 'Authorization required',\n })\n }\n\n const token = authHeader.substring(7)\n\n // Verify session token\n let payload\n try {\n payload = verifySessionToken(token)\n } catch (error: unknown) {\n return res.status(401).json({\n success: false,\n error: error instanceof Error ? error.message : 'Invalid token',\n })\n }\n\n const { name, locale, emailPreferences } = req.body\n\n // Prepare update data\n const updateData: any = {}\n \n if (name !== undefined) {\n updateData.name = name\n }\n \n if (locale !== undefined) {\n updateData.locale = locale\n }\n \n if (emailPreferences !== undefined) {\n updateData.emailPreferences = emailPreferences\n }\n\n // Update subscriber - use synthetic user to ensure only updating own data\n const subscriber = await req.payload.update({\n collection: config.subscribersSlug || 'subscribers',\n id: payload.subscriberId,\n data: updateData,\n overrideAccess: false,\n user: {\n collection: 'subscribers',\n id: payload.subscriberId,\n email: payload.email,\n },\n })\n\n res.json({\n success: true,\n subscriber: {\n id: subscriber.id,\n email: subscriber.email,\n name: subscriber.name,\n locale: subscriber.locale,\n emailPreferences: subscriber.emailPreferences,\n subscriptionStatus: subscriber.subscriptionStatus,\n },\n })\n } catch (error: unknown) {\n console.error('Update preferences error:', error)\n res.status(500).json({\n success: false,\n error: 'Failed to update preferences',\n })\n }\n }) as PayloadHandler,\n }\n}"],"names":["verifySessionToken","createPreferencesEndpoint","config","path","method","handler","req","res","authHeader","headers","authorization","startsWith","status","json","success","error","token","substring","payload","Error","message","subscriber","findByID","collection","subscribersSlug","id","subscriberId","overrideAccess","user","email","name","locale","emailPreferences","subscriptionStatus","console","createUpdatePreferencesEndpoint","body","updateData","undefined","update","data"],"mappings":"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"}
@@ -1,151 +0,0 @@
1
- import { isDomainAllowed, sanitizeInput, validateSubscriberData, extractUTMParams } from '../utils/validation';
2
- export const createSubscribeEndpoint = (config)=>{
3
- return {
4
- path: '/newsletter/subscribe',
5
- method: 'post',
6
- handler: async (req, res)=>{
7
- try {
8
- const { email, name, source, preferences, leadMagnet, surveyResponses, metadata = {} } = req.body;
9
- // Validate input
10
- const validation = validateSubscriberData({
11
- email,
12
- name,
13
- source
14
- });
15
- if (!validation.valid) {
16
- return res.status(400).json({
17
- success: false,
18
- errors: validation.errors
19
- });
20
- }
21
- // Check domain restrictions from active settings
22
- // Settings are public info needed for validation, but we can still respect access control
23
- const settingsResult = await req.payload.find({
24
- collection: config.settingsSlug || 'newsletter-settings',
25
- where: {
26
- active: {
27
- equals: true
28
- }
29
- },
30
- limit: 1,
31
- overrideAccess: false
32
- });
33
- const settings = settingsResult.docs[0];
34
- const allowedDomains = settings?.allowedDomains?.map((d)=>d.domain) || [];
35
- if (!isDomainAllowed(email, allowedDomains)) {
36
- return res.status(400).json({
37
- success: false,
38
- error: 'Email domain not allowed'
39
- });
40
- }
41
- // Check if already subscribed
42
- // This needs admin access to check for existing email
43
- const existing = await req.payload.find({
44
- collection: config.subscribersSlug || 'subscribers',
45
- where: {
46
- email: {
47
- equals: email.toLowerCase()
48
- }
49
- }
50
- });
51
- if (existing.docs.length > 0) {
52
- const subscriber = existing.docs[0];
53
- // If unsubscribed, don't allow resubscription via API
54
- if (subscriber.subscriptionStatus === 'unsubscribed') {
55
- return res.status(400).json({
56
- success: false,
57
- error: 'This email has been unsubscribed. Please contact support to resubscribe.'
58
- });
59
- }
60
- return res.status(400).json({
61
- success: false,
62
- error: 'Already subscribed',
63
- subscriber: {
64
- id: subscriber.id,
65
- email: subscriber.email,
66
- subscriptionStatus: subscriber.subscriptionStatus
67
- }
68
- });
69
- }
70
- // Check IP rate limiting
71
- const ipAddress = req.ip || req.connection.remoteAddress;
72
- const maxPerIP = settings?.maxSubscribersPerIP || 10;
73
- const ipSubscribers = await req.payload.find({
74
- collection: config.subscribersSlug || 'subscribers',
75
- where: {
76
- 'signupMetadata.ipAddress': {
77
- equals: ipAddress
78
- }
79
- }
80
- });
81
- if (ipSubscribers.docs.length >= maxPerIP) {
82
- return res.status(429).json({
83
- success: false,
84
- error: 'Too many subscriptions from this IP address'
85
- });
86
- }
87
- // Extract UTM parameters
88
- const referer = req.headers.referer || req.headers.referrer || '';
89
- const utmParams = extractUTMParams(new URL(referer).searchParams);
90
- // Prepare subscriber data
91
- const subscriberData = {
92
- email: email.toLowerCase(),
93
- name: name ? sanitizeInput(name) : undefined,
94
- locale: metadata.locale || config.i18n?.defaultLocale || 'en',
95
- subscriptionStatus: settings?.requireDoubleOptIn ? 'pending' : 'active',
96
- source: source || 'api',
97
- emailPreferences: {
98
- newsletter: true,
99
- announcements: true,
100
- ...preferences || {}
101
- },
102
- signupMetadata: {
103
- ipAddress,
104
- userAgent: req.headers['user-agent'],
105
- referrer: referer,
106
- signupPage: metadata.signupPage || referer
107
- }
108
- };
109
- // Add UTM parameters if tracking is enabled
110
- if (config.features?.utmTracking?.enabled && Object.keys(utmParams).length > 0) {
111
- subscriberData.utmParameters = utmParams;
112
- }
113
- // Add lead magnet if provided
114
- if (config.features?.leadMagnets?.enabled && leadMagnet) {
115
- subscriberData.leadMagnet = leadMagnet;
116
- }
117
- // Create subscriber
118
- // Public endpoint needs to create subscribers
119
- const subscriber = await req.payload.create({
120
- collection: config.subscribersSlug || 'subscribers',
121
- data: subscriberData
122
- });
123
- // Handle survey responses if provided
124
- if (config.features?.surveys?.enabled && surveyResponses) {
125
- // TODO: Store survey responses
126
- }
127
- // Send confirmation email if double opt-in
128
- if (settings?.requireDoubleOptIn) {
129
- // TODO: Send confirmation email with magic link
130
- }
131
- res.json({
132
- success: true,
133
- subscriber: {
134
- id: subscriber.id,
135
- email: subscriber.email,
136
- subscriptionStatus: subscriber.subscriptionStatus
137
- },
138
- message: settings?.requireDoubleOptIn ? 'Please check your email to confirm your subscription' : 'Successfully subscribed'
139
- });
140
- } catch (error) {
141
- console.error('Subscribe endpoint error:', error);
142
- res.status(500).json({
143
- success: false,
144
- error: 'Failed to subscribe. Please try again.'
145
- });
146
- }
147
- }
148
- };
149
- };
150
-
151
- //# sourceMappingURL=subscribe.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../src/endpoints/subscribe.ts"],"sourcesContent":["import type { Endpoint, PayloadHandler } from 'payload'\nimport type { NewsletterPluginConfig } from '../types'\nimport { \n isDomainAllowed, \n sanitizeInput, \n validateSubscriberData,\n extractUTMParams \n} from '../utils/validation'\n\nexport const createSubscribeEndpoint = (\n config: NewsletterPluginConfig\n): Endpoint => {\n return {\n path: '/newsletter/subscribe',\n method: 'post',\n handler: (async (req: any, res: any) => {\n try {\n const { \n email, \n name, \n source,\n preferences,\n leadMagnet,\n surveyResponses,\n metadata = {}\n } = req.body\n\n // Validate input\n const validation = validateSubscriberData({ email, name, source })\n if (!validation.valid) {\n return res.status(400).json({\n success: false,\n errors: validation.errors,\n })\n }\n\n // Check domain restrictions from active settings\n // Settings are public info needed for validation, but we can still respect access control\n const settingsResult = await req.payload.find({\n collection: config.settingsSlug || 'newsletter-settings',\n where: {\n active: {\n equals: true,\n },\n },\n limit: 1,\n overrideAccess: false,\n // No user context for public endpoint\n })\n \n const settings = settingsResult.docs[0]\n\n const allowedDomains = settings?.allowedDomains?.map((d: any) => d.domain) || []\n if (!isDomainAllowed(email, allowedDomains)) {\n return res.status(400).json({\n success: false,\n error: 'Email domain not allowed',\n })\n }\n\n // Check if already subscribed\n // This needs admin access to check for existing email\n const existing = await req.payload.find({\n collection: config.subscribersSlug || 'subscribers',\n where: {\n email: {\n equals: email.toLowerCase(),\n },\n },\n // Keep overrideAccess: true for public subscription check\n })\n\n if (existing.docs.length > 0) {\n const subscriber = existing.docs[0]\n \n // If unsubscribed, don't allow resubscription via API\n if (subscriber.subscriptionStatus === 'unsubscribed') {\n return res.status(400).json({\n success: false,\n error: 'This email has been unsubscribed. Please contact support to resubscribe.',\n })\n }\n\n return res.status(400).json({\n success: false,\n error: 'Already subscribed',\n subscriber: {\n id: subscriber.id,\n email: subscriber.email,\n subscriptionStatus: subscriber.subscriptionStatus,\n },\n })\n }\n\n // Check IP rate limiting\n const ipAddress = req.ip || req.connection.remoteAddress\n const maxPerIP = settings?.maxSubscribersPerIP || 10\n\n const ipSubscribers = await req.payload.find({\n collection: config.subscribersSlug || 'subscribers',\n where: {\n 'signupMetadata.ipAddress': {\n equals: ipAddress,\n },\n },\n // Keep overrideAccess: true for rate limiting check\n })\n\n if (ipSubscribers.docs.length >= maxPerIP) {\n return res.status(429).json({\n success: false,\n error: 'Too many subscriptions from this IP address',\n })\n }\n\n // Extract UTM parameters\n const referer = req.headers.referer || req.headers.referrer || ''\n const utmParams = extractUTMParams(new URL(referer).searchParams)\n\n // Prepare subscriber data\n const subscriberData: any = {\n email: email.toLowerCase(),\n name: name ? sanitizeInput(name) : undefined,\n locale: metadata.locale || config.i18n?.defaultLocale || 'en',\n subscriptionStatus: settings?.requireDoubleOptIn ? 'pending' : 'active',\n source: source || 'api',\n emailPreferences: {\n newsletter: true,\n announcements: true,\n ...(preferences || {}),\n },\n signupMetadata: {\n ipAddress,\n userAgent: req.headers['user-agent'],\n referrer: referer,\n signupPage: metadata.signupPage || referer,\n },\n }\n\n // Add UTM parameters if tracking is enabled\n if (config.features?.utmTracking?.enabled && Object.keys(utmParams).length > 0) {\n subscriberData.utmParameters = utmParams\n }\n\n // Add lead magnet if provided\n if (config.features?.leadMagnets?.enabled && leadMagnet) {\n subscriberData.leadMagnet = leadMagnet\n }\n\n // Create subscriber\n // Public endpoint needs to create subscribers\n const subscriber = await req.payload.create({\n collection: config.subscribersSlug || 'subscribers',\n data: subscriberData,\n // Keep overrideAccess: true for public subscription\n })\n\n // Handle survey responses if provided\n if (config.features?.surveys?.enabled && surveyResponses) {\n // TODO: Store survey responses\n }\n\n // Send confirmation email if double opt-in\n if (settings?.requireDoubleOptIn) {\n // TODO: Send confirmation email with magic link\n }\n\n res.json({\n success: true,\n subscriber: {\n id: subscriber.id,\n email: subscriber.email,\n subscriptionStatus: subscriber.subscriptionStatus,\n },\n message: settings?.requireDoubleOptIn \n ? 'Please check your email to confirm your subscription'\n : 'Successfully subscribed',\n })\n } catch (error) {\n console.error('Subscribe endpoint error:', error)\n res.status(500).json({\n success: false,\n error: 'Failed to subscribe. Please try again.',\n })\n }\n }) as PayloadHandler,\n }\n}"],"names":["isDomainAllowed","sanitizeInput","validateSubscriberData","extractUTMParams","createSubscribeEndpoint","config","path","method","handler","req","res","email","name","source","preferences","leadMagnet","surveyResponses","metadata","body","validation","valid","status","json","success","errors","settingsResult","payload","find","collection","settingsSlug","where","active","equals","limit","overrideAccess","settings","docs","allowedDomains","map","d","domain","error","existing","subscribersSlug","toLowerCase","length","subscriber","subscriptionStatus","id","ipAddress","ip","connection","remoteAddress","maxPerIP","maxSubscribersPerIP","ipSubscribers","referer","headers","referrer","utmParams","URL","searchParams","subscriberData","undefined","locale","i18n","defaultLocale","requireDoubleOptIn","emailPreferences","newsletter","announcements","signupMetadata","userAgent","signupPage","features","utmTracking","enabled","Object","keys","utmParameters","leadMagnets","create","data","surveys","message","console"],"mappings":"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"}
@@ -1,105 +0,0 @@
1
- import { isValidEmail } from '../utils/validation';
2
- export const createUnsubscribeEndpoint = (config)=>{
3
- return {
4
- path: '/newsletter/unsubscribe',
5
- method: 'post',
6
- handler: async (req, res)=>{
7
- try {
8
- const { email, token } = req.body;
9
- // Two methods: email or token
10
- if (!email && !token) {
11
- return res.status(400).json({
12
- success: false,
13
- error: 'Email or token is required'
14
- });
15
- }
16
- let subscriber;
17
- if (token) {
18
- // Token-based unsubscribe (from email link)
19
- try {
20
- const jwt = await import('jsonwebtoken');
21
- const payload = jwt.verify(token, process.env.JWT_SECRET || process.env.PAYLOAD_SECRET || '');
22
- if (payload.type !== 'unsubscribe') {
23
- throw new Error('Invalid token type');
24
- }
25
- // Token verified, so we can look up the subscriber
26
- // Using overrideAccess: true here is OK since we verified the token
27
- subscriber = await req.payload.findByID({
28
- collection: config.subscribersSlug || 'subscribers',
29
- id: payload.subscriberId
30
- });
31
- } catch {
32
- return res.status(401).json({
33
- success: false,
34
- error: 'Invalid or expired unsubscribe link'
35
- });
36
- }
37
- } else {
38
- // Email-based unsubscribe
39
- if (!isValidEmail(email)) {
40
- return res.status(400).json({
41
- success: false,
42
- error: 'Invalid email format'
43
- });
44
- }
45
- const result = await req.payload.find({
46
- collection: config.subscribersSlug || 'subscribers',
47
- where: {
48
- email: {
49
- equals: email.toLowerCase()
50
- }
51
- }
52
- });
53
- if (result.docs.length === 0) {
54
- // Don't reveal if email exists or not
55
- return res.json({
56
- success: true,
57
- message: 'If this email was subscribed, it has been unsubscribed.'
58
- });
59
- }
60
- subscriber = result.docs[0];
61
- }
62
- if (!subscriber) {
63
- return res.json({
64
- success: true,
65
- message: 'If this email was subscribed, it has been unsubscribed.'
66
- });
67
- }
68
- // Check if already unsubscribed
69
- if (subscriber.subscriptionStatus === 'unsubscribed') {
70
- return res.json({
71
- success: true,
72
- message: 'Already unsubscribed'
73
- });
74
- }
75
- // Update subscription status - use synthetic user to ensure proper access
76
- await req.payload.update({
77
- collection: config.subscribersSlug || 'subscribers',
78
- id: subscriber.id,
79
- data: {
80
- subscriptionStatus: 'unsubscribed',
81
- unsubscribedAt: new Date().toISOString()
82
- },
83
- overrideAccess: false,
84
- user: {
85
- collection: 'subscribers',
86
- id: subscriber.id,
87
- email: subscriber.email
88
- }
89
- });
90
- res.json({
91
- success: true,
92
- message: 'Successfully unsubscribed'
93
- });
94
- } catch (error) {
95
- console.error('Unsubscribe error:', error);
96
- res.status(500).json({
97
- success: false,
98
- error: 'Failed to unsubscribe. Please try again.'
99
- });
100
- }
101
- }
102
- };
103
- };
104
-
105
- //# sourceMappingURL=unsubscribe.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../src/endpoints/unsubscribe.ts"],"sourcesContent":["import type { Endpoint, PayloadHandler } from 'payload'\nimport type { NewsletterPluginConfig } from '../types'\nimport { isValidEmail } from '../utils/validation'\n\nexport const createUnsubscribeEndpoint = (\n config: NewsletterPluginConfig\n): Endpoint => {\n return {\n path: '/newsletter/unsubscribe',\n method: 'post',\n handler: (async (req: any, res: any) => {\n try {\n const { email, token } = req.body\n\n // Two methods: email or token\n if (!email && !token) {\n return res.status(400).json({\n success: false,\n error: 'Email or token is required',\n })\n }\n\n let subscriber\n\n if (token) {\n // Token-based unsubscribe (from email link)\n try {\n const jwt = await import('jsonwebtoken')\n const payload = jwt.verify(\n token,\n process.env.JWT_SECRET || process.env.PAYLOAD_SECRET || ''\n ) as any\n\n if (payload.type !== 'unsubscribe') {\n throw new Error('Invalid token type')\n }\n\n // Token verified, so we can look up the subscriber\n // Using overrideAccess: true here is OK since we verified the token\n subscriber = await req.payload.findByID({\n collection: config.subscribersSlug || 'subscribers',\n id: payload.subscriberId,\n })\n } catch {\n return res.status(401).json({\n success: false,\n error: 'Invalid or expired unsubscribe link',\n })\n }\n } else {\n // Email-based unsubscribe\n if (!isValidEmail(email)) {\n return res.status(400).json({\n success: false,\n error: 'Invalid email format',\n })\n }\n\n const result = await req.payload.find({\n collection: config.subscribersSlug || 'subscribers',\n where: {\n email: {\n equals: email.toLowerCase(),\n },\n },\n })\n\n if (result.docs.length === 0) {\n // Don't reveal if email exists or not\n return res.json({\n success: true,\n message: 'If this email was subscribed, it has been unsubscribed.',\n })\n }\n\n subscriber = result.docs[0]\n }\n\n if (!subscriber) {\n return res.json({\n success: true,\n message: 'If this email was subscribed, it has been unsubscribed.',\n })\n }\n\n // Check if already unsubscribed\n if (subscriber.subscriptionStatus === 'unsubscribed') {\n return res.json({\n success: true,\n message: 'Already unsubscribed',\n })\n }\n\n // Update subscription status - use synthetic user to ensure proper access\n await req.payload.update({\n collection: config.subscribersSlug || 'subscribers',\n id: subscriber.id,\n data: {\n subscriptionStatus: 'unsubscribed',\n unsubscribedAt: new Date().toISOString(),\n },\n overrideAccess: false,\n user: {\n collection: 'subscribers',\n id: subscriber.id,\n email: subscriber.email,\n },\n })\n\n res.json({\n success: true,\n message: 'Successfully unsubscribed',\n })\n } catch (error: unknown) {\n console.error('Unsubscribe error:', error)\n res.status(500).json({\n success: false,\n error: 'Failed to unsubscribe. Please try again.',\n })\n }\n }) as PayloadHandler,\n }\n}"],"names":["isValidEmail","createUnsubscribeEndpoint","config","path","method","handler","req","res","email","token","body","status","json","success","error","subscriber","jwt","payload","verify","process","env","JWT_SECRET","PAYLOAD_SECRET","type","Error","findByID","collection","subscribersSlug","id","subscriberId","result","find","where","equals","toLowerCase","docs","length","message","subscriptionStatus","update","data","unsubscribedAt","Date","toISOString","overrideAccess","user","console"],"mappings":"AAEA,SAASA,YAAY,QAAQ,sBAAqB;AAElD,OAAO,MAAMC,4BAA4B,CACvCC;IAEA,OAAO;QACLC,MAAM;QACNC,QAAQ;QACRC,SAAU,OAAOC,KAAUC;YACzB,IAAI;gBACF,MAAM,EAAEC,KAAK,EAAEC,KAAK,EAAE,GAAGH,IAAII,IAAI;gBAEjC,8BAA8B;gBAC9B,IAAI,CAACF,SAAS,CAACC,OAAO;oBACpB,OAAOF,IAAII,MAAM,CAAC,KAAKC,IAAI,CAAC;wBAC1BC,SAAS;wBACTC,OAAO;oBACT;gBACF;gBAEA,IAAIC;gBAEJ,IAAIN,OAAO;oBACT,4CAA4C;oBAC5C,IAAI;wBACF,MAAMO,MAAM,MAAM,MAAM,CAAC;wBACzB,MAAMC,UAAUD,IAAIE,MAAM,CACxBT,OACAU,QAAQC,GAAG,CAACC,UAAU,IAAIF,QAAQC,GAAG,CAACE,cAAc,IAAI;wBAG1D,IAAIL,QAAQM,IAAI,KAAK,eAAe;4BAClC,MAAM,IAAIC,MAAM;wBAClB;wBAEA,mDAAmD;wBACnD,oEAAoE;wBACpET,aAAa,MAAMT,IAAIW,OAAO,CAACQ,QAAQ,CAAC;4BACtCC,YAAYxB,OAAOyB,eAAe,IAAI;4BACtCC,IAAIX,QAAQY,YAAY;wBAC1B;oBACF,EAAE,OAAM;wBACN,OAAOtB,IAAII,MAAM,CAAC,KAAKC,IAAI,CAAC;4BAC1BC,SAAS;4BACTC,OAAO;wBACT;oBACF;gBACF,OAAO;oBACL,0BAA0B;oBAC1B,IAAI,CAACd,aAAaQ,QAAQ;wBACxB,OAAOD,IAAII,MAAM,CAAC,KAAKC,IAAI,CAAC;4BAC1BC,SAAS;4BACTC,OAAO;wBACT;oBACF;oBAEA,MAAMgB,SAAS,MAAMxB,IAAIW,OAAO,CAACc,IAAI,CAAC;wBACpCL,YAAYxB,OAAOyB,eAAe,IAAI;wBACtCK,OAAO;4BACLxB,OAAO;gCACLyB,QAAQzB,MAAM0B,WAAW;4BAC3B;wBACF;oBACF;oBAEA,IAAIJ,OAAOK,IAAI,CAACC,MAAM,KAAK,GAAG;wBAC5B,sCAAsC;wBACtC,OAAO7B,IAAIK,IAAI,CAAC;4BACdC,SAAS;4BACTwB,SAAS;wBACX;oBACF;oBAEAtB,aAAae,OAAOK,IAAI,CAAC,EAAE;gBAC7B;gBAEA,IAAI,CAACpB,YAAY;oBACf,OAAOR,IAAIK,IAAI,CAAC;wBACdC,SAAS;wBACTwB,SAAS;oBACX;gBACF;gBAEA,gCAAgC;gBAChC,IAAItB,WAAWuB,kBAAkB,KAAK,gBAAgB;oBACpD,OAAO/B,IAAIK,IAAI,CAAC;wBACdC,SAAS;wBACTwB,SAAS;oBACX;gBACF;gBAEA,0EAA0E;gBAC1E,MAAM/B,IAAIW,OAAO,CAACsB,MAAM,CAAC;oBACvBb,YAAYxB,OAAOyB,eAAe,IAAI;oBACtCC,IAAIb,WAAWa,EAAE;oBACjBY,MAAM;wBACJF,oBAAoB;wBACpBG,gBAAgB,IAAIC,OAAOC,WAAW;oBACxC;oBACAC,gBAAgB;oBAChBC,MAAM;wBACJnB,YAAY;wBACZE,IAAIb,WAAWa,EAAE;wBACjBpB,OAAOO,WAAWP,KAAK;oBACzB;gBACF;gBAEAD,IAAIK,IAAI,CAAC;oBACPC,SAAS;oBACTwB,SAAS;gBACX;YACF,EAAE,OAAOvB,OAAgB;gBACvBgC,QAAQhC,KAAK,CAAC,sBAAsBA;gBACpCP,IAAII,MAAM,CAAC,KAAKC,IAAI,CAAC;oBACnBC,SAAS;oBACTC,OAAO;gBACT;YACF;QACF;IACF;AACF,EAAC"}
@@ -1,103 +0,0 @@
1
- import { verifyMagicLinkToken, generateSessionToken } from '../utils/jwt';
2
- export const createVerifyMagicLinkEndpoint = (config)=>{
3
- return {
4
- path: '/newsletter/verify-magic-link',
5
- method: 'post',
6
- handler: async (req, res)=>{
7
- try {
8
- const { token } = req.body;
9
- if (!token) {
10
- return res.status(400).json({
11
- success: false,
12
- error: 'Token is required'
13
- });
14
- }
15
- // Verify the magic link token
16
- let payload;
17
- try {
18
- payload = verifyMagicLinkToken(token);
19
- } catch (error) {
20
- return res.status(401).json({
21
- success: false,
22
- error: error instanceof Error ? error.message : 'Invalid token'
23
- });
24
- }
25
- // Find the subscriber - token verified so we can use admin access for initial lookup
26
- const subscriber = await req.payload.findByID({
27
- collection: config.subscribersSlug || 'subscribers',
28
- id: payload.subscriberId
29
- });
30
- if (!subscriber) {
31
- return res.status(404).json({
32
- success: false,
33
- error: 'Subscriber not found'
34
- });
35
- }
36
- // Check if email matches
37
- if (subscriber.email !== payload.email) {
38
- return res.status(401).json({
39
- success: false,
40
- error: 'Invalid token'
41
- });
42
- }
43
- // Check if subscriber is active
44
- if (subscriber.subscriptionStatus === 'unsubscribed') {
45
- return res.status(403).json({
46
- success: false,
47
- error: 'This email has been unsubscribed'
48
- });
49
- }
50
- // Create synthetic user for subscriber operations
51
- const syntheticUser = {
52
- collection: 'subscribers',
53
- id: subscriber.id,
54
- email: subscriber.email
55
- };
56
- // Update subscription status if pending
57
- if (subscriber.subscriptionStatus === 'pending') {
58
- await req.payload.update({
59
- collection: config.subscribersSlug || 'subscribers',
60
- id: subscriber.id,
61
- data: {
62
- subscriptionStatus: 'active'
63
- },
64
- overrideAccess: false,
65
- user: syntheticUser
66
- });
67
- }
68
- // Clear the magic link token
69
- await req.payload.update({
70
- collection: config.subscribersSlug || 'subscribers',
71
- id: subscriber.id,
72
- data: {
73
- magicLinkToken: null,
74
- magicLinkTokenExpiry: null
75
- },
76
- overrideAccess: false,
77
- user: syntheticUser
78
- });
79
- // Generate session token
80
- const sessionToken = generateSessionToken(String(subscriber.id), subscriber.email);
81
- res.json({
82
- success: true,
83
- sessionToken,
84
- subscriber: {
85
- id: subscriber.id,
86
- email: subscriber.email,
87
- name: subscriber.name,
88
- locale: subscriber.locale,
89
- emailPreferences: subscriber.emailPreferences
90
- }
91
- });
92
- } catch (error) {
93
- console.error('Verify magic link error:', error);
94
- res.status(500).json({
95
- success: false,
96
- error: 'Failed to verify magic link'
97
- });
98
- }
99
- }
100
- };
101
- };
102
-
103
- //# sourceMappingURL=verify-magic-link.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../src/endpoints/verify-magic-link.ts"],"sourcesContent":["import type { Endpoint, PayloadHandler } from 'payload'\nimport type { NewsletterPluginConfig } from '../types'\nimport { \n verifyMagicLinkToken, \n generateSessionToken \n} from '../utils/jwt'\n\nexport const createVerifyMagicLinkEndpoint = (\n config: NewsletterPluginConfig\n): Endpoint => {\n return {\n path: '/newsletter/verify-magic-link',\n method: 'post',\n handler: (async (req: any, res: any) => {\n try {\n const { token } = req.body\n\n if (!token) {\n return res.status(400).json({\n success: false,\n error: 'Token is required',\n })\n }\n\n // Verify the magic link token\n let payload\n try {\n payload = verifyMagicLinkToken(token)\n } catch (error: unknown) {\n return res.status(401).json({\n success: false,\n error: error instanceof Error ? error.message : 'Invalid token',\n })\n }\n\n // Find the subscriber - token verified so we can use admin access for initial lookup\n const subscriber = await req.payload.findByID({\n collection: config.subscribersSlug || 'subscribers',\n id: payload.subscriberId,\n // Keep overrideAccess: true for token verification\n })\n\n if (!subscriber) {\n return res.status(404).json({\n success: false,\n error: 'Subscriber not found',\n })\n }\n\n // Check if email matches\n if (subscriber.email !== payload.email) {\n return res.status(401).json({\n success: false,\n error: 'Invalid token',\n })\n }\n\n // Check if subscriber is active\n if (subscriber.subscriptionStatus === 'unsubscribed') {\n return res.status(403).json({\n success: false,\n error: 'This email has been unsubscribed',\n })\n }\n\n // Create synthetic user for subscriber operations\n const syntheticUser = {\n collection: 'subscribers',\n id: subscriber.id,\n email: subscriber.email,\n }\n\n // Update subscription status if pending\n if (subscriber.subscriptionStatus === 'pending') {\n await req.payload.update({\n collection: config.subscribersSlug || 'subscribers',\n id: subscriber.id,\n data: {\n subscriptionStatus: 'active',\n },\n overrideAccess: false,\n user: syntheticUser,\n })\n }\n\n // Clear the magic link token\n await req.payload.update({\n collection: config.subscribersSlug || 'subscribers',\n id: subscriber.id,\n data: {\n magicLinkToken: null,\n magicLinkTokenExpiry: null,\n },\n overrideAccess: false,\n user: syntheticUser,\n })\n\n // Generate session token\n const sessionToken = generateSessionToken(\n String(subscriber.id),\n subscriber.email\n )\n\n res.json({\n success: true,\n sessionToken,\n subscriber: {\n id: subscriber.id,\n email: subscriber.email,\n name: subscriber.name,\n locale: subscriber.locale,\n emailPreferences: subscriber.emailPreferences,\n },\n })\n } catch (error: unknown) {\n console.error('Verify magic link error:', error)\n res.status(500).json({\n success: false,\n error: 'Failed to verify magic link',\n })\n }\n }) as PayloadHandler,\n }\n}"],"names":["verifyMagicLinkToken","generateSessionToken","createVerifyMagicLinkEndpoint","config","path","method","handler","req","res","token","body","status","json","success","error","payload","Error","message","subscriber","findByID","collection","subscribersSlug","id","subscriberId","email","subscriptionStatus","syntheticUser","update","data","overrideAccess","user","magicLinkToken","magicLinkTokenExpiry","sessionToken","String","name","locale","emailPreferences","console"],"mappings":"AAEA,SACEA,oBAAoB,EACpBC,oBAAoB,QACf,eAAc;AAErB,OAAO,MAAMC,gCAAgC,CAC3CC;IAEA,OAAO;QACLC,MAAM;QACNC,QAAQ;QACRC,SAAU,OAAOC,KAAUC;YACzB,IAAI;gBACF,MAAM,EAAEC,KAAK,EAAE,GAAGF,IAAIG,IAAI;gBAE1B,IAAI,CAACD,OAAO;oBACV,OAAOD,IAAIG,MAAM,CAAC,KAAKC,IAAI,CAAC;wBAC1BC,SAAS;wBACTC,OAAO;oBACT;gBACF;gBAEA,8BAA8B;gBAC9B,IAAIC;gBACJ,IAAI;oBACFA,UAAUf,qBAAqBS;gBACjC,EAAE,OAAOK,OAAgB;oBACvB,OAAON,IAAIG,MAAM,CAAC,KAAKC,IAAI,CAAC;wBAC1BC,SAAS;wBACTC,OAAOA,iBAAiBE,QAAQF,MAAMG,OAAO,GAAG;oBAClD;gBACF;gBAEA,qFAAqF;gBACrF,MAAMC,aAAa,MAAMX,IAAIQ,OAAO,CAACI,QAAQ,CAAC;oBAC5CC,YAAYjB,OAAOkB,eAAe,IAAI;oBACtCC,IAAIP,QAAQQ,YAAY;gBAE1B;gBAEA,IAAI,CAACL,YAAY;oBACf,OAAOV,IAAIG,MAAM,CAAC,KAAKC,IAAI,CAAC;wBAC1BC,SAAS;wBACTC,OAAO;oBACT;gBACF;gBAEA,yBAAyB;gBACzB,IAAII,WAAWM,KAAK,KAAKT,QAAQS,KAAK,EAAE;oBACtC,OAAOhB,IAAIG,MAAM,CAAC,KAAKC,IAAI,CAAC;wBAC1BC,SAAS;wBACTC,OAAO;oBACT;gBACF;gBAEA,gCAAgC;gBAChC,IAAII,WAAWO,kBAAkB,KAAK,gBAAgB;oBACpD,OAAOjB,IAAIG,MAAM,CAAC,KAAKC,IAAI,CAAC;wBAC1BC,SAAS;wBACTC,OAAO;oBACT;gBACF;gBAEA,kDAAkD;gBAClD,MAAMY,gBAAgB;oBACpBN,YAAY;oBACZE,IAAIJ,WAAWI,EAAE;oBACjBE,OAAON,WAAWM,KAAK;gBACzB;gBAEA,wCAAwC;gBACxC,IAAIN,WAAWO,kBAAkB,KAAK,WAAW;oBAC/C,MAAMlB,IAAIQ,OAAO,CAACY,MAAM,CAAC;wBACvBP,YAAYjB,OAAOkB,eAAe,IAAI;wBACtCC,IAAIJ,WAAWI,EAAE;wBACjBM,MAAM;4BACJH,oBAAoB;wBACtB;wBACAI,gBAAgB;wBAChBC,MAAMJ;oBACR;gBACF;gBAEA,6BAA6B;gBAC7B,MAAMnB,IAAIQ,OAAO,CAACY,MAAM,CAAC;oBACvBP,YAAYjB,OAAOkB,eAAe,IAAI;oBACtCC,IAAIJ,WAAWI,EAAE;oBACjBM,MAAM;wBACJG,gBAAgB;wBAChBC,sBAAsB;oBACxB;oBACAH,gBAAgB;oBAChBC,MAAMJ;gBACR;gBAEA,yBAAyB;gBACzB,MAAMO,eAAehC,qBACnBiC,OAAOhB,WAAWI,EAAE,GACpBJ,WAAWM,KAAK;gBAGlBhB,IAAII,IAAI,CAAC;oBACPC,SAAS;oBACToB;oBACAf,YAAY;wBACVI,IAAIJ,WAAWI,EAAE;wBACjBE,OAAON,WAAWM,KAAK;wBACvBW,MAAMjB,WAAWiB,IAAI;wBACrBC,QAAQlB,WAAWkB,MAAM;wBACzBC,kBAAkBnB,WAAWmB,gBAAgB;oBAC/C;gBACF;YACF,EAAE,OAAOvB,OAAgB;gBACvBwB,QAAQxB,KAAK,CAAC,4BAA4BA;gBAC1CN,IAAIG,MAAM,CAAC,KAAKC,IAAI,CAAC;oBACnBC,SAAS;oBACTC,OAAO;gBACT;YACF;QACF;IACF;AACF,EAAC"}
@@ -1,7 +0,0 @@
1
- 'use client';
2
- // React components
3
- export { NewsletterForm, createNewsletterForm, PreferencesForm, createPreferencesForm, MagicLinkVerify, createMagicLinkVerify } from '../components';
4
- // Hooks
5
- export { useNewsletterAuth } from '../hooks/useNewsletterAuth';
6
-
7
- //# sourceMappingURL=client.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../src/exports/client.ts"],"sourcesContent":["'use client'\n\n// React components\nexport { \n NewsletterForm, \n createNewsletterForm,\n PreferencesForm,\n createPreferencesForm,\n MagicLinkVerify,\n createMagicLinkVerify,\n} from '../components'\n\n// Hooks\nexport { useNewsletterAuth } from '../hooks/useNewsletterAuth'\n\n// Types for client-side use\nexport type {\n SignupFormProps,\n PreferencesFormProps,\n Subscriber,\n} from '../types'\n\nexport type {\n MagicLinkVerifyProps,\n} from '../components'\n\nexport type {\n UseNewsletterAuthOptions,\n UseNewsletterAuthReturn,\n} from '../hooks/useNewsletterAuth'"],"names":["NewsletterForm","createNewsletterForm","PreferencesForm","createPreferencesForm","MagicLinkVerify","createMagicLinkVerify","useNewsletterAuth"],"mappings":"AAAA;AAEA,mBAAmB;AACnB,SACEA,cAAc,EACdC,oBAAoB,EACpBC,eAAe,EACfC,qBAAqB,EACrBC,eAAe,EACfC,qBAAqB,QAChB,gBAAe;AAEtB,QAAQ;AACR,SAASC,iBAAiB,QAAQ,6BAA4B"}
@@ -1,6 +0,0 @@
1
- 'use client';
2
- // Re-export all components from the client export
3
- // This allows users to import directly from @payloadcms/plugin-newsletter/components
4
- export * from './client';
5
-
6
- //# sourceMappingURL=components.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../src/exports/components.ts"],"sourcesContent":["'use client'\n\n// Re-export all components from the client export\n// This allows users to import directly from @payloadcms/plugin-newsletter/components\nexport * from './client'"],"names":[],"mappings":"AAAA;AAEA,kDAAkD;AAClD,qFAAqF;AACrF,cAAc,WAAU"}
@@ -1,3 +0,0 @@
1
- export * from '../types';
2
-
3
- //# sourceMappingURL=types.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../src/exports/types.ts"],"sourcesContent":["export * from '../types'"],"names":[],"mappings":"AAAA,cAAc,WAAU"}