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
package/dist/client.js ADDED
@@ -0,0 +1,867 @@
1
+ "use client";
2
+
3
+ // src/components/NewsletterForm.tsx
4
+ import { useState } from "react";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+ var defaultStyles = {
7
+ form: {
8
+ display: "flex",
9
+ flexDirection: "column",
10
+ gap: "1rem",
11
+ maxWidth: "400px",
12
+ margin: "0 auto"
13
+ },
14
+ inputGroup: {
15
+ display: "flex",
16
+ flexDirection: "column",
17
+ gap: "0.5rem"
18
+ },
19
+ label: {
20
+ fontSize: "0.875rem",
21
+ fontWeight: "500",
22
+ color: "#374151"
23
+ },
24
+ input: {
25
+ padding: "0.5rem 0.75rem",
26
+ fontSize: "1rem",
27
+ border: "1px solid #e5e7eb",
28
+ borderRadius: "0.375rem",
29
+ outline: "none",
30
+ transition: "border-color 0.2s"
31
+ },
32
+ button: {
33
+ padding: "0.75rem 1.5rem",
34
+ fontSize: "1rem",
35
+ fontWeight: "500",
36
+ color: "#ffffff",
37
+ backgroundColor: "#3b82f6",
38
+ border: "none",
39
+ borderRadius: "0.375rem",
40
+ cursor: "pointer",
41
+ transition: "background-color 0.2s"
42
+ },
43
+ buttonDisabled: {
44
+ opacity: 0.5,
45
+ cursor: "not-allowed"
46
+ },
47
+ error: {
48
+ fontSize: "0.875rem",
49
+ color: "#ef4444",
50
+ marginTop: "0.25rem"
51
+ },
52
+ success: {
53
+ fontSize: "0.875rem",
54
+ color: "#10b981",
55
+ marginTop: "0.25rem"
56
+ },
57
+ checkbox: {
58
+ display: "flex",
59
+ alignItems: "center",
60
+ gap: "0.5rem"
61
+ },
62
+ checkboxInput: {
63
+ width: "1rem",
64
+ height: "1rem"
65
+ },
66
+ checkboxLabel: {
67
+ fontSize: "0.875rem",
68
+ color: "#374151"
69
+ }
70
+ };
71
+ var NewsletterForm = ({
72
+ onSuccess,
73
+ onError,
74
+ showName = false,
75
+ showPreferences = false,
76
+ leadMagnet,
77
+ className,
78
+ styles: customStyles = {},
79
+ apiEndpoint = "/api/newsletter/subscribe",
80
+ buttonText = "Subscribe",
81
+ loadingText = "Subscribing...",
82
+ successMessage = "Successfully subscribed!",
83
+ placeholders = {
84
+ email: "Enter your email",
85
+ name: "Enter your name"
86
+ },
87
+ labels = {
88
+ email: "Email",
89
+ name: "Name",
90
+ newsletter: "Newsletter updates",
91
+ announcements: "Product announcements"
92
+ }
93
+ }) => {
94
+ const [email, setEmail] = useState("");
95
+ const [name, setName] = useState("");
96
+ const [preferences, setPreferences] = useState({
97
+ newsletter: true,
98
+ announcements: true
99
+ });
100
+ const [loading, setLoading] = useState(false);
101
+ const [error, setError] = useState(null);
102
+ const [success, setSuccess] = useState(false);
103
+ const styles = {
104
+ form: { ...defaultStyles.form, ...customStyles.form },
105
+ inputGroup: { ...defaultStyles.inputGroup, ...customStyles.inputGroup },
106
+ label: { ...defaultStyles.label, ...customStyles.label },
107
+ input: { ...defaultStyles.input, ...customStyles.input },
108
+ button: { ...defaultStyles.button, ...customStyles.button },
109
+ buttonDisabled: { ...defaultStyles.buttonDisabled, ...customStyles.buttonDisabled },
110
+ error: { ...defaultStyles.error, ...customStyles.error },
111
+ success: { ...defaultStyles.success, ...customStyles.success },
112
+ checkbox: { ...defaultStyles.checkbox, ...customStyles.checkbox },
113
+ checkboxInput: { ...defaultStyles.checkboxInput, ...customStyles.checkboxInput },
114
+ checkboxLabel: { ...defaultStyles.checkboxLabel, ...customStyles.checkboxLabel }
115
+ };
116
+ const handleSubmit = async (e) => {
117
+ e.preventDefault();
118
+ setError(null);
119
+ setLoading(true);
120
+ try {
121
+ const payload = {
122
+ email,
123
+ ...showName && name && { name },
124
+ ...showPreferences && { preferences },
125
+ ...leadMagnet && { leadMagnet: leadMagnet.id },
126
+ metadata: {
127
+ signupPage: window.location.href,
128
+ ...typeof window !== "undefined" && window.location.search && {
129
+ utmParams: Object.fromEntries(new URLSearchParams(window.location.search))
130
+ }
131
+ }
132
+ };
133
+ const response = await fetch(apiEndpoint, {
134
+ method: "POST",
135
+ headers: {
136
+ "Content-Type": "application/json"
137
+ },
138
+ body: JSON.stringify(payload)
139
+ });
140
+ const data = await response.json();
141
+ if (!response.ok) {
142
+ throw new Error(data.error || data.errors?.join(", ") || "Subscription failed");
143
+ }
144
+ setSuccess(true);
145
+ setEmail("");
146
+ setName("");
147
+ if (onSuccess) {
148
+ onSuccess(data.subscriber);
149
+ }
150
+ } catch (err) {
151
+ const errorMessage = err instanceof Error ? err.message : "An error occurred";
152
+ setError(errorMessage);
153
+ if (onError) {
154
+ onError(new Error(errorMessage));
155
+ }
156
+ } finally {
157
+ setLoading(false);
158
+ }
159
+ };
160
+ if (success && !showPreferences) {
161
+ return /* @__PURE__ */ jsx("div", { className, style: styles.form, children: /* @__PURE__ */ jsx("p", { style: styles.success, children: successMessage }) });
162
+ }
163
+ return /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, className, style: styles.form, children: [
164
+ /* @__PURE__ */ jsxs("div", { style: styles.inputGroup, children: [
165
+ /* @__PURE__ */ jsx("label", { htmlFor: "email", style: styles.label, children: labels.email }),
166
+ /* @__PURE__ */ jsx(
167
+ "input",
168
+ {
169
+ id: "email",
170
+ type: "email",
171
+ value: email,
172
+ onChange: (e) => setEmail(e.target.value),
173
+ placeholder: placeholders.email,
174
+ required: true,
175
+ disabled: loading,
176
+ style: {
177
+ ...styles.input,
178
+ ...loading && { opacity: 0.5 }
179
+ }
180
+ }
181
+ )
182
+ ] }),
183
+ showName && /* @__PURE__ */ jsxs("div", { style: styles.inputGroup, children: [
184
+ /* @__PURE__ */ jsx("label", { htmlFor: "name", style: styles.label, children: labels.name }),
185
+ /* @__PURE__ */ jsx(
186
+ "input",
187
+ {
188
+ id: "name",
189
+ type: "text",
190
+ value: name,
191
+ onChange: (e) => setName(e.target.value),
192
+ placeholder: placeholders.name,
193
+ disabled: loading,
194
+ style: {
195
+ ...styles.input,
196
+ ...loading && { opacity: 0.5 }
197
+ }
198
+ }
199
+ )
200
+ ] }),
201
+ showPreferences && /* @__PURE__ */ jsxs("div", { style: styles.inputGroup, children: [
202
+ /* @__PURE__ */ jsx("label", { style: styles.label, children: "Email Preferences" }),
203
+ /* @__PURE__ */ jsxs("div", { style: styles.checkbox, children: [
204
+ /* @__PURE__ */ jsx(
205
+ "input",
206
+ {
207
+ id: "newsletter",
208
+ type: "checkbox",
209
+ checked: preferences.newsletter,
210
+ onChange: (e) => setPreferences({ ...preferences, newsletter: e.target.checked }),
211
+ disabled: loading,
212
+ style: styles.checkboxInput
213
+ }
214
+ ),
215
+ /* @__PURE__ */ jsx("label", { htmlFor: "newsletter", style: styles.checkboxLabel, children: labels.newsletter })
216
+ ] }),
217
+ /* @__PURE__ */ jsxs("div", { style: styles.checkbox, children: [
218
+ /* @__PURE__ */ jsx(
219
+ "input",
220
+ {
221
+ id: "announcements",
222
+ type: "checkbox",
223
+ checked: preferences.announcements,
224
+ onChange: (e) => setPreferences({ ...preferences, announcements: e.target.checked }),
225
+ disabled: loading,
226
+ style: styles.checkboxInput
227
+ }
228
+ ),
229
+ /* @__PURE__ */ jsx("label", { htmlFor: "announcements", style: styles.checkboxLabel, children: labels.announcements })
230
+ ] })
231
+ ] }),
232
+ /* @__PURE__ */ jsx(
233
+ "button",
234
+ {
235
+ type: "submit",
236
+ disabled: loading,
237
+ style: {
238
+ ...styles.button,
239
+ ...loading && styles.buttonDisabled
240
+ },
241
+ children: loading ? loadingText : buttonText
242
+ }
243
+ ),
244
+ error && /* @__PURE__ */ jsx("p", { style: styles.error, children: error }),
245
+ success && /* @__PURE__ */ jsx("p", { style: styles.success, children: successMessage })
246
+ ] });
247
+ };
248
+ function createNewsletterForm(defaultProps) {
249
+ return (props) => /* @__PURE__ */ jsx(NewsletterForm, { ...defaultProps, ...props });
250
+ }
251
+
252
+ // src/components/PreferencesForm.tsx
253
+ import { useState as useState2, useEffect } from "react";
254
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
255
+ var defaultStyles2 = {
256
+ container: {
257
+ maxWidth: "600px",
258
+ margin: "0 auto",
259
+ padding: "2rem"
260
+ },
261
+ heading: {
262
+ fontSize: "1.5rem",
263
+ fontWeight: "600",
264
+ marginBottom: "1.5rem",
265
+ color: "#111827"
266
+ },
267
+ form: {
268
+ display: "flex",
269
+ flexDirection: "column",
270
+ gap: "1.5rem"
271
+ },
272
+ section: {
273
+ padding: "1.5rem",
274
+ backgroundColor: "#f9fafb",
275
+ borderRadius: "0.5rem",
276
+ border: "1px solid #e5e7eb"
277
+ },
278
+ sectionTitle: {
279
+ fontSize: "1.125rem",
280
+ fontWeight: "500",
281
+ marginBottom: "1rem",
282
+ color: "#111827"
283
+ },
284
+ inputGroup: {
285
+ display: "flex",
286
+ flexDirection: "column",
287
+ gap: "0.5rem"
288
+ },
289
+ label: {
290
+ fontSize: "0.875rem",
291
+ fontWeight: "500",
292
+ color: "#374151"
293
+ },
294
+ input: {
295
+ padding: "0.5rem 0.75rem",
296
+ fontSize: "1rem",
297
+ border: "1px solid #e5e7eb",
298
+ borderRadius: "0.375rem",
299
+ outline: "none",
300
+ transition: "border-color 0.2s"
301
+ },
302
+ select: {
303
+ padding: "0.5rem 0.75rem",
304
+ fontSize: "1rem",
305
+ border: "1px solid #e5e7eb",
306
+ borderRadius: "0.375rem",
307
+ outline: "none",
308
+ backgroundColor: "#ffffff"
309
+ },
310
+ checkbox: {
311
+ display: "flex",
312
+ alignItems: "center",
313
+ gap: "0.5rem",
314
+ marginBottom: "0.5rem"
315
+ },
316
+ checkboxInput: {
317
+ width: "1rem",
318
+ height: "1rem"
319
+ },
320
+ checkboxLabel: {
321
+ fontSize: "0.875rem",
322
+ color: "#374151"
323
+ },
324
+ buttonGroup: {
325
+ display: "flex",
326
+ gap: "1rem",
327
+ marginTop: "1rem"
328
+ },
329
+ button: {
330
+ padding: "0.75rem 1.5rem",
331
+ fontSize: "1rem",
332
+ fontWeight: "500",
333
+ borderRadius: "0.375rem",
334
+ cursor: "pointer",
335
+ transition: "all 0.2s",
336
+ border: "none"
337
+ },
338
+ primaryButton: {
339
+ color: "#ffffff",
340
+ backgroundColor: "#3b82f6"
341
+ },
342
+ secondaryButton: {
343
+ color: "#374151",
344
+ backgroundColor: "#ffffff",
345
+ border: "1px solid #e5e7eb"
346
+ },
347
+ dangerButton: {
348
+ color: "#ffffff",
349
+ backgroundColor: "#ef4444"
350
+ },
351
+ error: {
352
+ fontSize: "0.875rem",
353
+ color: "#ef4444",
354
+ marginTop: "0.5rem"
355
+ },
356
+ success: {
357
+ fontSize: "0.875rem",
358
+ color: "#10b981",
359
+ marginTop: "0.5rem"
360
+ },
361
+ info: {
362
+ fontSize: "0.875rem",
363
+ color: "#6b7280",
364
+ marginTop: "0.5rem"
365
+ }
366
+ };
367
+ var PreferencesForm = ({
368
+ subscriber: initialSubscriber,
369
+ onSuccess,
370
+ onError,
371
+ className,
372
+ styles: customStyles = {},
373
+ sessionToken,
374
+ apiEndpoint = "/api/newsletter/preferences",
375
+ showUnsubscribe = true,
376
+ locales = ["en"],
377
+ labels = {
378
+ title: "Newsletter Preferences",
379
+ personalInfo: "Personal Information",
380
+ emailPreferences: "Email Preferences",
381
+ name: "Name",
382
+ language: "Preferred Language",
383
+ newsletter: "Newsletter updates",
384
+ announcements: "Product announcements",
385
+ saveButton: "Save Preferences",
386
+ unsubscribeButton: "Unsubscribe",
387
+ saving: "Saving...",
388
+ saved: "Preferences saved successfully!",
389
+ unsubscribeConfirm: "Are you sure you want to unsubscribe? This cannot be undone."
390
+ }
391
+ }) => {
392
+ const [subscriber, setSubscriber] = useState2(initialSubscriber || {});
393
+ const [loading, setLoading] = useState2(false);
394
+ const [loadingData, setLoadingData] = useState2(!initialSubscriber);
395
+ const [error, setError] = useState2(null);
396
+ const [success, setSuccess] = useState2(false);
397
+ const styles = {
398
+ container: { ...defaultStyles2.container, ...customStyles.container },
399
+ heading: { ...defaultStyles2.heading, ...customStyles.heading },
400
+ form: { ...defaultStyles2.form, ...customStyles.form },
401
+ section: { ...defaultStyles2.section, ...customStyles.section },
402
+ sectionTitle: { ...defaultStyles2.sectionTitle, ...customStyles.sectionTitle },
403
+ inputGroup: { ...defaultStyles2.inputGroup, ...customStyles.inputGroup },
404
+ label: { ...defaultStyles2.label, ...customStyles.label },
405
+ input: { ...defaultStyles2.input, ...customStyles.input },
406
+ select: { ...defaultStyles2.select, ...customStyles.select },
407
+ checkbox: { ...defaultStyles2.checkbox, ...customStyles.checkbox },
408
+ checkboxInput: { ...defaultStyles2.checkboxInput, ...customStyles.checkboxInput },
409
+ checkboxLabel: { ...defaultStyles2.checkboxLabel, ...customStyles.checkboxLabel },
410
+ buttonGroup: { ...defaultStyles2.buttonGroup, ...customStyles.buttonGroup },
411
+ button: { ...defaultStyles2.button, ...customStyles.button },
412
+ primaryButton: { ...defaultStyles2.primaryButton, ...customStyles.primaryButton },
413
+ secondaryButton: { ...defaultStyles2.secondaryButton, ...customStyles.secondaryButton },
414
+ dangerButton: { ...defaultStyles2.dangerButton, ...customStyles.dangerButton },
415
+ error: { ...defaultStyles2.error, ...customStyles.error },
416
+ success: { ...defaultStyles2.success, ...customStyles.success },
417
+ info: { ...defaultStyles2.info, ...customStyles.info }
418
+ };
419
+ useEffect(() => {
420
+ if (!initialSubscriber && sessionToken) {
421
+ fetchPreferences();
422
+ }
423
+ }, []);
424
+ const fetchPreferences = async () => {
425
+ try {
426
+ const response = await fetch(apiEndpoint, {
427
+ headers: {
428
+ "Authorization": `Bearer ${sessionToken}`
429
+ }
430
+ });
431
+ if (!response.ok) {
432
+ throw new Error("Failed to load preferences");
433
+ }
434
+ const data = await response.json();
435
+ setSubscriber(data.subscriber);
436
+ } catch (err) {
437
+ setError(err instanceof Error ? err.message : "Failed to load preferences");
438
+ if (onError) {
439
+ onError(err instanceof Error ? err : new Error("Failed to load preferences"));
440
+ }
441
+ } finally {
442
+ setLoadingData(false);
443
+ }
444
+ };
445
+ const handleSave = async (e) => {
446
+ e.preventDefault();
447
+ setError(null);
448
+ setSuccess(false);
449
+ setLoading(true);
450
+ try {
451
+ const response = await fetch(apiEndpoint, {
452
+ method: "POST",
453
+ headers: {
454
+ "Content-Type": "application/json",
455
+ "Authorization": `Bearer ${sessionToken}`
456
+ },
457
+ body: JSON.stringify({
458
+ name: subscriber.name,
459
+ locale: subscriber.locale,
460
+ emailPreferences: subscriber.emailPreferences
461
+ })
462
+ });
463
+ const data = await response.json();
464
+ if (!response.ok) {
465
+ throw new Error(data.error || "Failed to save preferences");
466
+ }
467
+ setSubscriber(data.subscriber);
468
+ setSuccess(true);
469
+ if (onSuccess) {
470
+ onSuccess(data.subscriber);
471
+ }
472
+ } catch (err) {
473
+ const errorMessage = err instanceof Error ? err.message : "An error occurred";
474
+ setError(errorMessage);
475
+ if (onError) {
476
+ onError(new Error(errorMessage));
477
+ }
478
+ } finally {
479
+ setLoading(false);
480
+ }
481
+ };
482
+ const handleUnsubscribe = async () => {
483
+ if (!window.confirm(labels.unsubscribeConfirm)) {
484
+ return;
485
+ }
486
+ setLoading(true);
487
+ setError(null);
488
+ try {
489
+ const response = await fetch("/api/newsletter/unsubscribe", {
490
+ method: "POST",
491
+ headers: {
492
+ "Content-Type": "application/json",
493
+ "Authorization": `Bearer ${sessionToken}`
494
+ },
495
+ body: JSON.stringify({
496
+ email: subscriber.email
497
+ })
498
+ });
499
+ if (!response.ok) {
500
+ throw new Error("Failed to unsubscribe");
501
+ }
502
+ setSubscriber({ ...subscriber, subscriptionStatus: "unsubscribed" });
503
+ if (onSuccess) {
504
+ onSuccess({ ...subscriber, subscriptionStatus: "unsubscribed" });
505
+ }
506
+ } catch (err) {
507
+ setError("Failed to unsubscribe. Please try again.");
508
+ if (onError) {
509
+ onError(err instanceof Error ? err : new Error("Failed to unsubscribe"));
510
+ }
511
+ } finally {
512
+ setLoading(false);
513
+ }
514
+ };
515
+ if (loadingData) {
516
+ return /* @__PURE__ */ jsx2("div", { className, style: styles.container, children: /* @__PURE__ */ jsx2("p", { style: styles.info, children: "Loading preferences..." }) });
517
+ }
518
+ if (subscriber.subscriptionStatus === "unsubscribed") {
519
+ return /* @__PURE__ */ jsxs2("div", { className, style: styles.container, children: [
520
+ /* @__PURE__ */ jsx2("h2", { style: styles.heading, children: "Unsubscribed" }),
521
+ /* @__PURE__ */ jsx2("p", { style: styles.info, children: "You have been unsubscribed from all emails. To resubscribe, please sign up again." })
522
+ ] });
523
+ }
524
+ return /* @__PURE__ */ jsxs2("div", { className, style: styles.container, children: [
525
+ /* @__PURE__ */ jsx2("h2", { style: styles.heading, children: labels.title }),
526
+ /* @__PURE__ */ jsxs2("form", { onSubmit: handleSave, style: styles.form, children: [
527
+ /* @__PURE__ */ jsxs2("div", { style: styles.section, children: [
528
+ /* @__PURE__ */ jsx2("h3", { style: styles.sectionTitle, children: labels.personalInfo }),
529
+ /* @__PURE__ */ jsxs2("div", { style: styles.inputGroup, children: [
530
+ /* @__PURE__ */ jsx2("label", { htmlFor: "name", style: styles.label, children: labels.name }),
531
+ /* @__PURE__ */ jsx2(
532
+ "input",
533
+ {
534
+ id: "name",
535
+ type: "text",
536
+ value: subscriber.name || "",
537
+ onChange: (e) => setSubscriber({ ...subscriber, name: e.target.value }),
538
+ disabled: loading,
539
+ style: styles.input
540
+ }
541
+ )
542
+ ] }),
543
+ locales.length > 1 && /* @__PURE__ */ jsxs2("div", { style: styles.inputGroup, children: [
544
+ /* @__PURE__ */ jsx2("label", { htmlFor: "locale", style: styles.label, children: labels.language }),
545
+ /* @__PURE__ */ jsx2(
546
+ "select",
547
+ {
548
+ id: "locale",
549
+ value: subscriber.locale || locales[0],
550
+ onChange: (e) => setSubscriber({ ...subscriber, locale: e.target.value }),
551
+ disabled: loading,
552
+ style: styles.select,
553
+ children: locales.map((locale) => /* @__PURE__ */ jsx2("option", { value: locale, children: locale.toUpperCase() }, locale))
554
+ }
555
+ )
556
+ ] })
557
+ ] }),
558
+ /* @__PURE__ */ jsxs2("div", { style: styles.section, children: [
559
+ /* @__PURE__ */ jsx2("h3", { style: styles.sectionTitle, children: labels.emailPreferences }),
560
+ /* @__PURE__ */ jsxs2("div", { style: styles.checkbox, children: [
561
+ /* @__PURE__ */ jsx2(
562
+ "input",
563
+ {
564
+ id: "pref-newsletter",
565
+ type: "checkbox",
566
+ checked: subscriber.emailPreferences?.newsletter ?? true,
567
+ onChange: (e) => setSubscriber({
568
+ ...subscriber,
569
+ emailPreferences: {
570
+ ...subscriber.emailPreferences,
571
+ newsletter: e.target.checked
572
+ }
573
+ }),
574
+ disabled: loading,
575
+ style: styles.checkboxInput
576
+ }
577
+ ),
578
+ /* @__PURE__ */ jsx2("label", { htmlFor: "pref-newsletter", style: styles.checkboxLabel, children: labels.newsletter })
579
+ ] }),
580
+ /* @__PURE__ */ jsxs2("div", { style: styles.checkbox, children: [
581
+ /* @__PURE__ */ jsx2(
582
+ "input",
583
+ {
584
+ id: "pref-announcements",
585
+ type: "checkbox",
586
+ checked: subscriber.emailPreferences?.announcements ?? true,
587
+ onChange: (e) => setSubscriber({
588
+ ...subscriber,
589
+ emailPreferences: {
590
+ ...subscriber.emailPreferences,
591
+ announcements: e.target.checked
592
+ }
593
+ }),
594
+ disabled: loading,
595
+ style: styles.checkboxInput
596
+ }
597
+ ),
598
+ /* @__PURE__ */ jsx2("label", { htmlFor: "pref-announcements", style: styles.checkboxLabel, children: labels.announcements })
599
+ ] })
600
+ ] }),
601
+ /* @__PURE__ */ jsxs2("div", { style: styles.buttonGroup, children: [
602
+ /* @__PURE__ */ jsx2(
603
+ "button",
604
+ {
605
+ type: "submit",
606
+ disabled: loading,
607
+ style: {
608
+ ...styles.button,
609
+ ...styles.primaryButton,
610
+ ...loading && { opacity: 0.5, cursor: "not-allowed" }
611
+ },
612
+ children: loading ? labels.saving : labels.saveButton
613
+ }
614
+ ),
615
+ showUnsubscribe && /* @__PURE__ */ jsx2(
616
+ "button",
617
+ {
618
+ type: "button",
619
+ onClick: handleUnsubscribe,
620
+ disabled: loading,
621
+ style: {
622
+ ...styles.button,
623
+ ...styles.dangerButton,
624
+ ...loading && { opacity: 0.5, cursor: "not-allowed" }
625
+ },
626
+ children: labels.unsubscribeButton
627
+ }
628
+ )
629
+ ] }),
630
+ error && /* @__PURE__ */ jsx2("p", { style: styles.error, children: error }),
631
+ success && /* @__PURE__ */ jsx2("p", { style: styles.success, children: labels.saved })
632
+ ] })
633
+ ] });
634
+ };
635
+ function createPreferencesForm(defaultProps) {
636
+ return (props) => /* @__PURE__ */ jsx2(PreferencesForm, { ...defaultProps, ...props });
637
+ }
638
+
639
+ // src/components/MagicLinkVerify.tsx
640
+ import { useState as useState3, useEffect as useEffect2 } from "react";
641
+ import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
642
+ var defaultStyles3 = {
643
+ container: {
644
+ maxWidth: "400px",
645
+ margin: "4rem auto",
646
+ padding: "2rem",
647
+ textAlign: "center"
648
+ },
649
+ heading: {
650
+ fontSize: "1.5rem",
651
+ fontWeight: "600",
652
+ marginBottom: "1rem",
653
+ color: "#111827"
654
+ },
655
+ message: {
656
+ fontSize: "1rem",
657
+ color: "#6b7280",
658
+ marginBottom: "1.5rem"
659
+ },
660
+ error: {
661
+ fontSize: "1rem",
662
+ color: "#ef4444",
663
+ marginBottom: "1.5rem"
664
+ },
665
+ button: {
666
+ padding: "0.75rem 1.5rem",
667
+ fontSize: "1rem",
668
+ fontWeight: "500",
669
+ color: "#ffffff",
670
+ backgroundColor: "#3b82f6",
671
+ border: "none",
672
+ borderRadius: "0.375rem",
673
+ cursor: "pointer",
674
+ transition: "background-color 0.2s"
675
+ }
676
+ };
677
+ var MagicLinkVerify = ({
678
+ token: propToken,
679
+ onSuccess,
680
+ onError,
681
+ apiEndpoint = "/api/newsletter/verify-magic-link",
682
+ className,
683
+ styles: customStyles = {},
684
+ labels = {
685
+ verifying: "Verifying your magic link...",
686
+ success: "Successfully verified! Redirecting...",
687
+ error: "Failed to verify magic link",
688
+ expired: "This magic link has expired. Please request a new one.",
689
+ invalid: "This magic link is invalid. Please request a new one.",
690
+ redirecting: "Redirecting to your preferences...",
691
+ tryAgain: "Try Again"
692
+ }
693
+ }) => {
694
+ const [status, setStatus] = useState3("verifying");
695
+ const [error, setError] = useState3(null);
696
+ const [_sessionToken, setSessionToken] = useState3(null);
697
+ const styles = {
698
+ container: { ...defaultStyles3.container, ...customStyles.container },
699
+ heading: { ...defaultStyles3.heading, ...customStyles.heading },
700
+ message: { ...defaultStyles3.message, ...customStyles.message },
701
+ error: { ...defaultStyles3.error, ...customStyles.error },
702
+ button: { ...defaultStyles3.button, ...customStyles.button }
703
+ };
704
+ useEffect2(() => {
705
+ const token = propToken || new URLSearchParams(window.location.search).get("token");
706
+ if (token) {
707
+ verifyToken(token);
708
+ } else {
709
+ setStatus("error");
710
+ setError(labels.invalid || "Invalid magic link");
711
+ }
712
+ }, [propToken]);
713
+ const verifyToken = async (token) => {
714
+ try {
715
+ const response = await fetch(apiEndpoint, {
716
+ method: "POST",
717
+ headers: {
718
+ "Content-Type": "application/json"
719
+ },
720
+ body: JSON.stringify({ token })
721
+ });
722
+ const data = await response.json();
723
+ if (!response.ok) {
724
+ if (data.error?.includes("expired")) {
725
+ throw new Error(labels.expired);
726
+ }
727
+ throw new Error(data.error || labels.error);
728
+ }
729
+ setStatus("success");
730
+ setSessionToken(data.sessionToken);
731
+ if (typeof window !== "undefined" && data.sessionToken) {
732
+ localStorage.setItem("newsletter_session", data.sessionToken);
733
+ }
734
+ if (onSuccess) {
735
+ onSuccess(data.sessionToken, data.subscriber);
736
+ }
737
+ } catch (err) {
738
+ setStatus("error");
739
+ const errorMessage = err instanceof Error ? err.message : labels.error || "Verification failed";
740
+ setError(errorMessage);
741
+ if (onError) {
742
+ onError(err instanceof Error ? err : new Error(errorMessage));
743
+ }
744
+ }
745
+ };
746
+ const handleTryAgain = () => {
747
+ window.location.href = "/";
748
+ };
749
+ return /* @__PURE__ */ jsxs3("div", { className, style: styles.container, children: [
750
+ status === "verifying" && /* @__PURE__ */ jsxs3(Fragment, { children: [
751
+ /* @__PURE__ */ jsx3("h2", { style: styles.heading, children: "Verifying" }),
752
+ /* @__PURE__ */ jsx3("p", { style: styles.message, children: labels.verifying })
753
+ ] }),
754
+ status === "success" && /* @__PURE__ */ jsxs3(Fragment, { children: [
755
+ /* @__PURE__ */ jsx3("h2", { style: styles.heading, children: "Success!" }),
756
+ /* @__PURE__ */ jsx3("p", { style: styles.message, children: labels.success })
757
+ ] }),
758
+ status === "error" && /* @__PURE__ */ jsxs3(Fragment, { children: [
759
+ /* @__PURE__ */ jsx3("h2", { style: styles.heading, children: "Verification Failed" }),
760
+ /* @__PURE__ */ jsx3("p", { style: styles.error, children: error }),
761
+ /* @__PURE__ */ jsx3("button", { onClick: handleTryAgain, style: styles.button, children: labels.tryAgain })
762
+ ] })
763
+ ] });
764
+ };
765
+ function createMagicLinkVerify(defaultProps) {
766
+ return (props) => /* @__PURE__ */ jsx3(MagicLinkVerify, { ...defaultProps, ...props });
767
+ }
768
+
769
+ // src/hooks/useNewsletterAuth.ts
770
+ import { useState as useState4, useEffect as useEffect3, useCallback } from "react";
771
+ function useNewsletterAuth(options = {}) {
772
+ const {
773
+ sessionTokenKey = "newsletter_session",
774
+ apiEndpoint = "/api/newsletter/preferences"
775
+ } = options;
776
+ const [subscriber, setSubscriber] = useState4(null);
777
+ const [loading, setLoading] = useState4(true);
778
+ const [error, setError] = useState4(null);
779
+ const getSessionToken = useCallback(() => {
780
+ if (typeof window === "undefined") return null;
781
+ return localStorage.getItem(sessionTokenKey);
782
+ }, [sessionTokenKey]);
783
+ const setSessionToken = useCallback((token) => {
784
+ if (typeof window === "undefined") return;
785
+ if (token) {
786
+ localStorage.setItem(sessionTokenKey, token);
787
+ } else {
788
+ localStorage.removeItem(sessionTokenKey);
789
+ }
790
+ }, [sessionTokenKey]);
791
+ const fetchSubscriber = useCallback(async (token) => {
792
+ try {
793
+ const response = await fetch(apiEndpoint, {
794
+ headers: {
795
+ "Authorization": `Bearer ${token}`
796
+ }
797
+ });
798
+ if (!response.ok) {
799
+ if (response.status === 401) {
800
+ setSessionToken(null);
801
+ throw new Error("Session expired");
802
+ }
803
+ throw new Error("Failed to fetch subscriber");
804
+ }
805
+ const data = await response.json();
806
+ setSubscriber(data.subscriber);
807
+ setError(null);
808
+ } catch (err) {
809
+ setError(err instanceof Error ? err : new Error("An error occurred"));
810
+ setSubscriber(null);
811
+ throw err;
812
+ }
813
+ }, [apiEndpoint, setSessionToken]);
814
+ useEffect3(() => {
815
+ const token = getSessionToken();
816
+ if (token) {
817
+ fetchSubscriber(token).catch(() => {
818
+ }).finally(() => setLoading(false));
819
+ } else {
820
+ setLoading(false);
821
+ }
822
+ }, []);
823
+ const login = useCallback(async (token) => {
824
+ setLoading(true);
825
+ setError(null);
826
+ try {
827
+ setSessionToken(token);
828
+ await fetchSubscriber(token);
829
+ } catch (err) {
830
+ setSessionToken(null);
831
+ throw err;
832
+ } finally {
833
+ setLoading(false);
834
+ }
835
+ }, [fetchSubscriber, setSessionToken]);
836
+ const logout = useCallback(() => {
837
+ setSessionToken(null);
838
+ setSubscriber(null);
839
+ setError(null);
840
+ }, [setSessionToken]);
841
+ const refreshSubscriber = useCallback(async () => {
842
+ const token = getSessionToken();
843
+ if (!token) {
844
+ throw new Error("Not authenticated");
845
+ }
846
+ await fetchSubscriber(token);
847
+ }, [fetchSubscriber, getSessionToken]);
848
+ return {
849
+ subscriber,
850
+ loading,
851
+ error,
852
+ isAuthenticated: !!subscriber,
853
+ login,
854
+ logout,
855
+ refreshSubscriber
856
+ };
857
+ }
858
+ export {
859
+ MagicLinkVerify,
860
+ NewsletterForm,
861
+ PreferencesForm,
862
+ createMagicLinkVerify,
863
+ createNewsletterForm,
864
+ createPreferencesForm,
865
+ useNewsletterAuth
866
+ };
867
+ //# sourceMappingURL=client.js.map