@volchoklv/newsletter-kit 1.0.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/README.md +505 -0
- package/dist/adapters/email/index.d.ts +119 -0
- package/dist/adapters/email/index.js +417 -0
- package/dist/adapters/email/index.js.map +1 -0
- package/dist/adapters/storage/index.d.ts +215 -0
- package/dist/adapters/storage/index.js +415 -0
- package/dist/adapters/storage/index.js.map +1 -0
- package/dist/components/index.d.ts +198 -0
- package/dist/components/index.js +505 -0
- package/dist/components/index.js.map +1 -0
- package/dist/index.d.ts +73 -0
- package/dist/index.js +1762 -0
- package/dist/index.js.map +1 -0
- package/dist/server/index.d.ts +77 -0
- package/dist/server/index.js +530 -0
- package/dist/server/index.js.map +1 -0
- package/dist/types-BmajlhNp.d.ts +226 -0
- package/package.json +95 -0
|
@@ -0,0 +1,505 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/components/newsletter-form.tsx
|
|
4
|
+
import { useState, useCallback } from "react";
|
|
5
|
+
|
|
6
|
+
// src/utils/cn.ts
|
|
7
|
+
function cn(...inputs) {
|
|
8
|
+
return inputs.filter(Boolean).join(" ");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// src/components/newsletter-form.tsx
|
|
12
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
13
|
+
function NewsletterForm({
|
|
14
|
+
endpoint = "/api/newsletter/subscribe",
|
|
15
|
+
source,
|
|
16
|
+
tags,
|
|
17
|
+
honeypotField = "website",
|
|
18
|
+
className,
|
|
19
|
+
formClassName,
|
|
20
|
+
inputClassName,
|
|
21
|
+
buttonClassName,
|
|
22
|
+
messageClassName,
|
|
23
|
+
placeholder = "Enter your email",
|
|
24
|
+
buttonText = "Subscribe",
|
|
25
|
+
loadingText = "Subscribing...",
|
|
26
|
+
successMessage,
|
|
27
|
+
errorMessage,
|
|
28
|
+
showMessage = true,
|
|
29
|
+
onSuccess,
|
|
30
|
+
onError,
|
|
31
|
+
onSubmit,
|
|
32
|
+
disabled = false,
|
|
33
|
+
metadata
|
|
34
|
+
}) {
|
|
35
|
+
const [state, setState] = useState({
|
|
36
|
+
status: "idle",
|
|
37
|
+
message: "",
|
|
38
|
+
email: ""
|
|
39
|
+
});
|
|
40
|
+
const handleSubmit = useCallback(
|
|
41
|
+
async (e) => {
|
|
42
|
+
e.preventDefault();
|
|
43
|
+
const formData = new FormData(e.currentTarget);
|
|
44
|
+
const email = formData.get("email");
|
|
45
|
+
const honeypot = formData.get(honeypotField);
|
|
46
|
+
if (honeypot) {
|
|
47
|
+
setState({
|
|
48
|
+
status: "success",
|
|
49
|
+
message: successMessage || "Thanks for subscribing!",
|
|
50
|
+
email: ""
|
|
51
|
+
});
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (!email || !email.includes("@")) {
|
|
55
|
+
setState({
|
|
56
|
+
status: "error",
|
|
57
|
+
message: "Please enter a valid email address.",
|
|
58
|
+
email
|
|
59
|
+
});
|
|
60
|
+
onError?.("Please enter a valid email address.");
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
setState({ status: "loading", message: "", email });
|
|
64
|
+
onSubmit?.(email);
|
|
65
|
+
try {
|
|
66
|
+
const response = await fetch(endpoint, {
|
|
67
|
+
method: "POST",
|
|
68
|
+
headers: { "Content-Type": "application/json" },
|
|
69
|
+
body: JSON.stringify({
|
|
70
|
+
email,
|
|
71
|
+
source,
|
|
72
|
+
tags,
|
|
73
|
+
metadata
|
|
74
|
+
})
|
|
75
|
+
});
|
|
76
|
+
const data = await response.json();
|
|
77
|
+
if (response.ok && data.success) {
|
|
78
|
+
const message = successMessage || data.message || "Thanks for subscribing!";
|
|
79
|
+
setState({
|
|
80
|
+
status: "success",
|
|
81
|
+
message,
|
|
82
|
+
email: ""
|
|
83
|
+
});
|
|
84
|
+
onSuccess?.(email, message);
|
|
85
|
+
} else {
|
|
86
|
+
const message = errorMessage || data.message || "Something went wrong. Please try again.";
|
|
87
|
+
setState({
|
|
88
|
+
status: "error",
|
|
89
|
+
message,
|
|
90
|
+
email
|
|
91
|
+
});
|
|
92
|
+
onError?.(message);
|
|
93
|
+
}
|
|
94
|
+
} catch (error) {
|
|
95
|
+
const message = errorMessage || "Network error. Please try again.";
|
|
96
|
+
setState({
|
|
97
|
+
status: "error",
|
|
98
|
+
message,
|
|
99
|
+
email
|
|
100
|
+
});
|
|
101
|
+
onError?.(message);
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
[endpoint, source, tags, metadata, honeypotField, successMessage, errorMessage, onSuccess, onError, onSubmit]
|
|
105
|
+
);
|
|
106
|
+
const isLoading = state.status === "loading";
|
|
107
|
+
const isDisabled = disabled || isLoading;
|
|
108
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("w-full", className), children: [
|
|
109
|
+
/* @__PURE__ */ jsxs(
|
|
110
|
+
"form",
|
|
111
|
+
{
|
|
112
|
+
onSubmit: handleSubmit,
|
|
113
|
+
className: cn("flex flex-col sm:flex-row gap-2", formClassName),
|
|
114
|
+
children: [
|
|
115
|
+
/* @__PURE__ */ jsx(
|
|
116
|
+
"input",
|
|
117
|
+
{
|
|
118
|
+
type: "text",
|
|
119
|
+
name: honeypotField,
|
|
120
|
+
autoComplete: "off",
|
|
121
|
+
tabIndex: -1,
|
|
122
|
+
"aria-hidden": "true",
|
|
123
|
+
className: "absolute -left-[9999px] opacity-0 pointer-events-none"
|
|
124
|
+
}
|
|
125
|
+
),
|
|
126
|
+
/* @__PURE__ */ jsx(
|
|
127
|
+
"input",
|
|
128
|
+
{
|
|
129
|
+
type: "email",
|
|
130
|
+
name: "email",
|
|
131
|
+
placeholder,
|
|
132
|
+
required: true,
|
|
133
|
+
disabled: isDisabled,
|
|
134
|
+
defaultValue: state.email,
|
|
135
|
+
"aria-label": "Email address",
|
|
136
|
+
"aria-describedby": state.message ? "newsletter-message" : void 0,
|
|
137
|
+
className: cn(
|
|
138
|
+
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm",
|
|
139
|
+
"ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium",
|
|
140
|
+
"placeholder:text-muted-foreground",
|
|
141
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
142
|
+
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
143
|
+
inputClassName
|
|
144
|
+
)
|
|
145
|
+
}
|
|
146
|
+
),
|
|
147
|
+
/* @__PURE__ */ jsx(
|
|
148
|
+
"button",
|
|
149
|
+
{
|
|
150
|
+
type: "submit",
|
|
151
|
+
disabled: isDisabled,
|
|
152
|
+
className: cn(
|
|
153
|
+
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium",
|
|
154
|
+
"ring-offset-background transition-colors",
|
|
155
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
156
|
+
"disabled:pointer-events-none disabled:opacity-50",
|
|
157
|
+
"bg-primary text-primary-foreground hover:bg-primary/90",
|
|
158
|
+
"h-10 px-4 py-2",
|
|
159
|
+
"min-w-[120px]",
|
|
160
|
+
buttonClassName
|
|
161
|
+
),
|
|
162
|
+
children: isLoading ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
163
|
+
/* @__PURE__ */ jsxs(
|
|
164
|
+
"svg",
|
|
165
|
+
{
|
|
166
|
+
className: "mr-2 h-4 w-4 animate-spin",
|
|
167
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
168
|
+
fill: "none",
|
|
169
|
+
viewBox: "0 0 24 24",
|
|
170
|
+
children: [
|
|
171
|
+
/* @__PURE__ */ jsx(
|
|
172
|
+
"circle",
|
|
173
|
+
{
|
|
174
|
+
className: "opacity-25",
|
|
175
|
+
cx: "12",
|
|
176
|
+
cy: "12",
|
|
177
|
+
r: "10",
|
|
178
|
+
stroke: "currentColor",
|
|
179
|
+
strokeWidth: "4"
|
|
180
|
+
}
|
|
181
|
+
),
|
|
182
|
+
/* @__PURE__ */ jsx(
|
|
183
|
+
"path",
|
|
184
|
+
{
|
|
185
|
+
className: "opacity-75",
|
|
186
|
+
fill: "currentColor",
|
|
187
|
+
d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
|
188
|
+
}
|
|
189
|
+
)
|
|
190
|
+
]
|
|
191
|
+
}
|
|
192
|
+
),
|
|
193
|
+
loadingText
|
|
194
|
+
] }) : buttonText
|
|
195
|
+
}
|
|
196
|
+
)
|
|
197
|
+
]
|
|
198
|
+
}
|
|
199
|
+
),
|
|
200
|
+
showMessage && state.message && /* @__PURE__ */ jsx(
|
|
201
|
+
"p",
|
|
202
|
+
{
|
|
203
|
+
id: "newsletter-message",
|
|
204
|
+
role: state.status === "error" ? "alert" : "status",
|
|
205
|
+
className: cn(
|
|
206
|
+
"mt-2 text-sm",
|
|
207
|
+
state.status === "success" && "text-green-600 dark:text-green-400",
|
|
208
|
+
state.status === "error" && "text-red-600 dark:text-red-400",
|
|
209
|
+
messageClassName
|
|
210
|
+
),
|
|
211
|
+
children: state.message
|
|
212
|
+
}
|
|
213
|
+
)
|
|
214
|
+
] });
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// src/components/newsletter-blocks.tsx
|
|
218
|
+
import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
219
|
+
function NewsletterBlock({
|
|
220
|
+
title = "Subscribe to our newsletter",
|
|
221
|
+
description = "Get the latest updates delivered directly to your inbox.",
|
|
222
|
+
containerClassName,
|
|
223
|
+
titleClassName,
|
|
224
|
+
descriptionClassName,
|
|
225
|
+
...formProps
|
|
226
|
+
}) {
|
|
227
|
+
return /* @__PURE__ */ jsx2(
|
|
228
|
+
"section",
|
|
229
|
+
{
|
|
230
|
+
className: cn(
|
|
231
|
+
"w-full py-12 md:py-16 lg:py-20",
|
|
232
|
+
"bg-muted/50",
|
|
233
|
+
containerClassName
|
|
234
|
+
),
|
|
235
|
+
children: /* @__PURE__ */ jsx2("div", { className: "container mx-auto px-4 md:px-6", children: /* @__PURE__ */ jsxs2("div", { className: "flex flex-col items-center justify-center space-y-4 text-center", children: [
|
|
236
|
+
/* @__PURE__ */ jsxs2("div", { className: "space-y-2", children: [
|
|
237
|
+
/* @__PURE__ */ jsx2(
|
|
238
|
+
"h2",
|
|
239
|
+
{
|
|
240
|
+
className: cn(
|
|
241
|
+
"text-2xl font-bold tracking-tight sm:text-3xl md:text-4xl",
|
|
242
|
+
titleClassName
|
|
243
|
+
),
|
|
244
|
+
children: title
|
|
245
|
+
}
|
|
246
|
+
),
|
|
247
|
+
/* @__PURE__ */ jsx2(
|
|
248
|
+
"p",
|
|
249
|
+
{
|
|
250
|
+
className: cn(
|
|
251
|
+
"max-w-[600px] text-muted-foreground md:text-lg",
|
|
252
|
+
descriptionClassName
|
|
253
|
+
),
|
|
254
|
+
children: description
|
|
255
|
+
}
|
|
256
|
+
)
|
|
257
|
+
] }),
|
|
258
|
+
/* @__PURE__ */ jsx2("div", { className: "w-full max-w-md", children: /* @__PURE__ */ jsx2(NewsletterForm, { ...formProps }) })
|
|
259
|
+
] }) })
|
|
260
|
+
}
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
function NewsletterCard({
|
|
264
|
+
title = "Newsletter",
|
|
265
|
+
description = "Subscribe to get updates.",
|
|
266
|
+
cardClassName,
|
|
267
|
+
titleClassName,
|
|
268
|
+
descriptionClassName,
|
|
269
|
+
...formProps
|
|
270
|
+
}) {
|
|
271
|
+
return /* @__PURE__ */ jsx2(
|
|
272
|
+
"div",
|
|
273
|
+
{
|
|
274
|
+
className: cn(
|
|
275
|
+
"rounded-lg border bg-card text-card-foreground shadow-sm",
|
|
276
|
+
"p-6",
|
|
277
|
+
cardClassName
|
|
278
|
+
),
|
|
279
|
+
children: /* @__PURE__ */ jsxs2("div", { className: "space-y-4", children: [
|
|
280
|
+
/* @__PURE__ */ jsxs2("div", { className: "space-y-1.5", children: [
|
|
281
|
+
/* @__PURE__ */ jsx2(
|
|
282
|
+
"h3",
|
|
283
|
+
{
|
|
284
|
+
className: cn(
|
|
285
|
+
"text-lg font-semibold leading-none tracking-tight",
|
|
286
|
+
titleClassName
|
|
287
|
+
),
|
|
288
|
+
children: title
|
|
289
|
+
}
|
|
290
|
+
),
|
|
291
|
+
/* @__PURE__ */ jsx2(
|
|
292
|
+
"p",
|
|
293
|
+
{
|
|
294
|
+
className: cn(
|
|
295
|
+
"text-sm text-muted-foreground",
|
|
296
|
+
descriptionClassName
|
|
297
|
+
),
|
|
298
|
+
children: description
|
|
299
|
+
}
|
|
300
|
+
)
|
|
301
|
+
] }),
|
|
302
|
+
/* @__PURE__ */ jsx2(NewsletterForm, { ...formProps })
|
|
303
|
+
] })
|
|
304
|
+
}
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
function NewsletterInline({
|
|
308
|
+
label,
|
|
309
|
+
labelClassName,
|
|
310
|
+
...formProps
|
|
311
|
+
}) {
|
|
312
|
+
return /* @__PURE__ */ jsxs2("div", { className: "flex flex-col gap-2", children: [
|
|
313
|
+
label && /* @__PURE__ */ jsx2(
|
|
314
|
+
"label",
|
|
315
|
+
{
|
|
316
|
+
className: cn(
|
|
317
|
+
"text-sm font-medium leading-none",
|
|
318
|
+
labelClassName
|
|
319
|
+
),
|
|
320
|
+
children: label
|
|
321
|
+
}
|
|
322
|
+
),
|
|
323
|
+
/* @__PURE__ */ jsx2(NewsletterForm, { ...formProps })
|
|
324
|
+
] });
|
|
325
|
+
}
|
|
326
|
+
function NewsletterModalContent({
|
|
327
|
+
title = "Subscribe to our newsletter",
|
|
328
|
+
description = "We'll send you the best content, no spam.",
|
|
329
|
+
titleClassName,
|
|
330
|
+
descriptionClassName,
|
|
331
|
+
...formProps
|
|
332
|
+
}) {
|
|
333
|
+
return /* @__PURE__ */ jsxs2("div", { className: "space-y-4", children: [
|
|
334
|
+
/* @__PURE__ */ jsxs2("div", { className: "space-y-2 text-center", children: [
|
|
335
|
+
/* @__PURE__ */ jsx2(
|
|
336
|
+
"h2",
|
|
337
|
+
{
|
|
338
|
+
className: cn(
|
|
339
|
+
"text-lg font-semibold leading-none tracking-tight",
|
|
340
|
+
titleClassName
|
|
341
|
+
),
|
|
342
|
+
children: title
|
|
343
|
+
}
|
|
344
|
+
),
|
|
345
|
+
/* @__PURE__ */ jsx2(
|
|
346
|
+
"p",
|
|
347
|
+
{
|
|
348
|
+
className: cn(
|
|
349
|
+
"text-sm text-muted-foreground",
|
|
350
|
+
descriptionClassName
|
|
351
|
+
),
|
|
352
|
+
children: description
|
|
353
|
+
}
|
|
354
|
+
)
|
|
355
|
+
] }),
|
|
356
|
+
/* @__PURE__ */ jsx2(
|
|
357
|
+
NewsletterForm,
|
|
358
|
+
{
|
|
359
|
+
...formProps,
|
|
360
|
+
formClassName: "flex-col",
|
|
361
|
+
buttonClassName: "w-full"
|
|
362
|
+
}
|
|
363
|
+
)
|
|
364
|
+
] });
|
|
365
|
+
}
|
|
366
|
+
function NewsletterFooter({
|
|
367
|
+
title = "Newsletter",
|
|
368
|
+
description,
|
|
369
|
+
containerClassName,
|
|
370
|
+
titleClassName,
|
|
371
|
+
descriptionClassName,
|
|
372
|
+
privacyText,
|
|
373
|
+
privacyLink,
|
|
374
|
+
...formProps
|
|
375
|
+
}) {
|
|
376
|
+
return /* @__PURE__ */ jsxs2("div", { className: cn("space-y-4", containerClassName), children: [
|
|
377
|
+
title && /* @__PURE__ */ jsx2(
|
|
378
|
+
"h3",
|
|
379
|
+
{
|
|
380
|
+
className: cn(
|
|
381
|
+
"text-sm font-semibold uppercase tracking-wider",
|
|
382
|
+
titleClassName
|
|
383
|
+
),
|
|
384
|
+
children: title
|
|
385
|
+
}
|
|
386
|
+
),
|
|
387
|
+
description && /* @__PURE__ */ jsx2(
|
|
388
|
+
"p",
|
|
389
|
+
{
|
|
390
|
+
className: cn(
|
|
391
|
+
"text-sm text-muted-foreground",
|
|
392
|
+
descriptionClassName
|
|
393
|
+
),
|
|
394
|
+
children: description
|
|
395
|
+
}
|
|
396
|
+
),
|
|
397
|
+
/* @__PURE__ */ jsx2(NewsletterForm, { ...formProps }),
|
|
398
|
+
privacyText && /* @__PURE__ */ jsx2("p", { className: "text-xs text-muted-foreground", children: privacyLink ? /* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
399
|
+
privacyText.replace(/\.$/, ""),
|
|
400
|
+
".",
|
|
401
|
+
" ",
|
|
402
|
+
/* @__PURE__ */ jsx2("a", { href: privacyLink, className: "underline hover:text-foreground", children: "Privacy Policy" })
|
|
403
|
+
] }) : privacyText })
|
|
404
|
+
] });
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// src/hooks/use-newsletter.ts
|
|
408
|
+
import { useState as useState2, useCallback as useCallback2 } from "react";
|
|
409
|
+
function useNewsletter(options = {}) {
|
|
410
|
+
const {
|
|
411
|
+
endpoint = "/api/newsletter/subscribe",
|
|
412
|
+
source,
|
|
413
|
+
tags,
|
|
414
|
+
metadata,
|
|
415
|
+
onSuccess,
|
|
416
|
+
onError
|
|
417
|
+
} = options;
|
|
418
|
+
const [state, setState] = useState2({
|
|
419
|
+
status: "idle",
|
|
420
|
+
message: "",
|
|
421
|
+
email: null
|
|
422
|
+
});
|
|
423
|
+
const subscribe = useCallback2(
|
|
424
|
+
async (email) => {
|
|
425
|
+
if (!email || !email.includes("@")) {
|
|
426
|
+
const errorMsg = "Please enter a valid email address.";
|
|
427
|
+
setState({
|
|
428
|
+
status: "error",
|
|
429
|
+
message: errorMsg,
|
|
430
|
+
email
|
|
431
|
+
});
|
|
432
|
+
onError?.(errorMsg);
|
|
433
|
+
return false;
|
|
434
|
+
}
|
|
435
|
+
setState({ status: "loading", message: "", email });
|
|
436
|
+
try {
|
|
437
|
+
const response = await fetch(endpoint, {
|
|
438
|
+
method: "POST",
|
|
439
|
+
headers: { "Content-Type": "application/json" },
|
|
440
|
+
body: JSON.stringify({
|
|
441
|
+
email,
|
|
442
|
+
source,
|
|
443
|
+
tags,
|
|
444
|
+
metadata
|
|
445
|
+
})
|
|
446
|
+
});
|
|
447
|
+
const data = await response.json();
|
|
448
|
+
if (response.ok && data.success) {
|
|
449
|
+
const message = data.message || "Thanks for subscribing!";
|
|
450
|
+
setState({
|
|
451
|
+
status: "success",
|
|
452
|
+
message,
|
|
453
|
+
email
|
|
454
|
+
});
|
|
455
|
+
onSuccess?.(email, message);
|
|
456
|
+
return true;
|
|
457
|
+
} else {
|
|
458
|
+
const message = data.message || "Something went wrong. Please try again.";
|
|
459
|
+
setState({
|
|
460
|
+
status: "error",
|
|
461
|
+
message,
|
|
462
|
+
email
|
|
463
|
+
});
|
|
464
|
+
onError?.(message);
|
|
465
|
+
return false;
|
|
466
|
+
}
|
|
467
|
+
} catch (error) {
|
|
468
|
+
const message = "Network error. Please try again.";
|
|
469
|
+
setState({
|
|
470
|
+
status: "error",
|
|
471
|
+
message,
|
|
472
|
+
email
|
|
473
|
+
});
|
|
474
|
+
onError?.(message);
|
|
475
|
+
return false;
|
|
476
|
+
}
|
|
477
|
+
},
|
|
478
|
+
[endpoint, source, tags, metadata, onSuccess, onError]
|
|
479
|
+
);
|
|
480
|
+
const reset = useCallback2(() => {
|
|
481
|
+
setState({
|
|
482
|
+
status: "idle",
|
|
483
|
+
message: "",
|
|
484
|
+
email: null
|
|
485
|
+
});
|
|
486
|
+
}, []);
|
|
487
|
+
return {
|
|
488
|
+
...state,
|
|
489
|
+
subscribe,
|
|
490
|
+
reset,
|
|
491
|
+
isLoading: state.status === "loading",
|
|
492
|
+
isSuccess: state.status === "success",
|
|
493
|
+
isError: state.status === "error"
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
export {
|
|
497
|
+
NewsletterBlock,
|
|
498
|
+
NewsletterCard,
|
|
499
|
+
NewsletterFooter,
|
|
500
|
+
NewsletterForm,
|
|
501
|
+
NewsletterInline,
|
|
502
|
+
NewsletterModalContent,
|
|
503
|
+
useNewsletter
|
|
504
|
+
};
|
|
505
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/newsletter-form.tsx","../../src/utils/cn.ts","../../src/components/newsletter-blocks.tsx","../../src/hooks/use-newsletter.ts"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { useState, useCallback, FormEvent } from 'react';\nimport type { NewsletterFormProps, NewsletterFormState } from '../types';\nimport { cn } from '../utils/cn';\n\n/**\n * Newsletter subscription form component\n * \n * Designed to work with shadcn/ui but includes default Tailwind styles.\n * \n * @example\n * ```tsx\n * // Basic usage\n * <NewsletterForm endpoint=\"/api/newsletter/subscribe\" />\n * \n * // With all options\n * <NewsletterForm\n * endpoint=\"/api/newsletter/subscribe\"\n * source=\"footer\"\n * tags={['marketing']}\n * placeholder=\"Enter your email\"\n * buttonText=\"Subscribe\"\n * successMessage=\"Check your inbox!\"\n * onSuccess={(email) => console.log('Subscribed:', email)}\n * onError={(error) => console.error('Error:', error)}\n * className=\"max-w-md\"\n * />\n * ```\n */\nexport function NewsletterForm({\n endpoint = '/api/newsletter/subscribe',\n source,\n tags,\n honeypotField = 'website',\n className,\n formClassName,\n inputClassName,\n buttonClassName,\n messageClassName,\n placeholder = 'Enter your email',\n buttonText = 'Subscribe',\n loadingText = 'Subscribing...',\n successMessage,\n errorMessage,\n showMessage = true,\n onSuccess,\n onError,\n onSubmit,\n disabled = false,\n metadata,\n}: NewsletterFormProps) {\n const [state, setState] = useState<NewsletterFormState>({\n status: 'idle',\n message: '',\n email: '',\n });\n\n const handleSubmit = useCallback(\n async (e: FormEvent<HTMLFormElement>) => {\n e.preventDefault();\n\n const formData = new FormData(e.currentTarget);\n const email = formData.get('email') as string;\n const honeypot = formData.get(honeypotField) as string;\n\n // If honeypot is filled, silently \"succeed\" (bot detection)\n if (honeypot) {\n setState({\n status: 'success',\n message: successMessage || 'Thanks for subscribing!',\n email: '',\n });\n return;\n }\n\n if (!email || !email.includes('@')) {\n setState({\n status: 'error',\n message: 'Please enter a valid email address.',\n email,\n });\n onError?.('Please enter a valid email address.');\n return;\n }\n\n setState({ status: 'loading', message: '', email });\n onSubmit?.(email);\n\n try {\n const response = await fetch(endpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n email,\n source,\n tags,\n metadata,\n }),\n });\n\n const data = await response.json();\n\n if (response.ok && data.success) {\n const message = successMessage || data.message || 'Thanks for subscribing!';\n setState({\n status: 'success',\n message,\n email: '',\n });\n onSuccess?.(email, message);\n } else {\n const message = errorMessage || data.message || 'Something went wrong. Please try again.';\n setState({\n status: 'error',\n message,\n email,\n });\n onError?.(message);\n }\n } catch (error) {\n const message = errorMessage || 'Network error. Please try again.';\n setState({\n status: 'error',\n message,\n email,\n });\n onError?.(message);\n }\n },\n [endpoint, source, tags, metadata, honeypotField, successMessage, errorMessage, onSuccess, onError, onSubmit]\n );\n\n const isLoading = state.status === 'loading';\n const isDisabled = disabled || isLoading;\n\n return (\n <div className={cn('w-full', className)}>\n <form\n onSubmit={handleSubmit}\n className={cn('flex flex-col sm:flex-row gap-2', formClassName)}\n >\n {/* Honeypot field - hidden from users, visible to bots */}\n <input\n type=\"text\"\n name={honeypotField}\n autoComplete=\"off\"\n tabIndex={-1}\n aria-hidden=\"true\"\n className=\"absolute -left-[9999px] opacity-0 pointer-events-none\"\n />\n\n <input\n type=\"email\"\n name=\"email\"\n placeholder={placeholder}\n required\n disabled={isDisabled}\n defaultValue={state.email}\n aria-label=\"Email address\"\n aria-describedby={state.message ? 'newsletter-message' : undefined}\n className={cn(\n 'flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm',\n 'ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium',\n 'placeholder:text-muted-foreground',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',\n 'disabled:cursor-not-allowed disabled:opacity-50',\n inputClassName\n )}\n />\n\n <button\n type=\"submit\"\n disabled={isDisabled}\n className={cn(\n 'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium',\n 'ring-offset-background transition-colors',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',\n 'disabled:pointer-events-none disabled:opacity-50',\n 'bg-primary text-primary-foreground hover:bg-primary/90',\n 'h-10 px-4 py-2',\n 'min-w-[120px]',\n buttonClassName\n )}\n >\n {isLoading ? (\n <>\n <svg\n className=\"mr-2 h-4 w-4 animate-spin\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n >\n <circle\n className=\"opacity-25\"\n cx=\"12\"\n cy=\"12\"\n r=\"10\"\n stroke=\"currentColor\"\n strokeWidth=\"4\"\n />\n <path\n className=\"opacity-75\"\n fill=\"currentColor\"\n d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\n />\n </svg>\n {loadingText}\n </>\n ) : (\n buttonText\n )}\n </button>\n </form>\n\n {showMessage && state.message && (\n <p\n id=\"newsletter-message\"\n role={state.status === 'error' ? 'alert' : 'status'}\n className={cn(\n 'mt-2 text-sm',\n state.status === 'success' && 'text-green-600 dark:text-green-400',\n state.status === 'error' && 'text-red-600 dark:text-red-400',\n messageClassName\n )}\n >\n {state.message}\n </p>\n )}\n </div>\n );\n}\n","/**\n * Utility for merging class names\n * Compatible with shadcn/ui's cn function\n */\nexport function cn(...inputs: (string | undefined | null | false)[]): string {\n return inputs.filter(Boolean).join(' ');\n}\n","'use client';\n\nimport * as React from 'react';\nimport { NewsletterForm } from './newsletter-form';\nimport type { NewsletterFormProps } from '../types';\nimport { cn } from '../utils/cn';\n\n// ============================================================================\n// Newsletter Block - Full width section\n// ============================================================================\n\ninterface NewsletterBlockProps extends NewsletterFormProps {\n title?: string;\n description?: string;\n containerClassName?: string;\n titleClassName?: string;\n descriptionClassName?: string;\n}\n\n/**\n * Full-width newsletter subscription block\n * \n * @example\n * ```tsx\n * <NewsletterBlock\n * endpoint=\"/api/newsletter/subscribe\"\n * title=\"Stay updated\"\n * description=\"Get the latest news delivered to your inbox.\"\n * source=\"footer\"\n * />\n * ```\n */\nexport function NewsletterBlock({\n title = 'Subscribe to our newsletter',\n description = 'Get the latest updates delivered directly to your inbox.',\n containerClassName,\n titleClassName,\n descriptionClassName,\n ...formProps\n}: NewsletterBlockProps) {\n return (\n <section\n className={cn(\n 'w-full py-12 md:py-16 lg:py-20',\n 'bg-muted/50',\n containerClassName\n )}\n >\n <div className=\"container mx-auto px-4 md:px-6\">\n <div className=\"flex flex-col items-center justify-center space-y-4 text-center\">\n <div className=\"space-y-2\">\n <h2\n className={cn(\n 'text-2xl font-bold tracking-tight sm:text-3xl md:text-4xl',\n titleClassName\n )}\n >\n {title}\n </h2>\n <p\n className={cn(\n 'max-w-[600px] text-muted-foreground md:text-lg',\n descriptionClassName\n )}\n >\n {description}\n </p>\n </div>\n <div className=\"w-full max-w-md\">\n <NewsletterForm {...formProps} />\n </div>\n </div>\n </div>\n </section>\n );\n}\n\n// ============================================================================\n// Newsletter Card - Contained card variant\n// ============================================================================\n\ninterface NewsletterCardProps extends NewsletterFormProps {\n title?: string;\n description?: string;\n cardClassName?: string;\n titleClassName?: string;\n descriptionClassName?: string;\n}\n\n/**\n * Card-style newsletter subscription component\n * \n * @example\n * ```tsx\n * <NewsletterCard\n * endpoint=\"/api/newsletter/subscribe\"\n * title=\"Join the community\"\n * description=\"Be the first to know about new features.\"\n * source=\"sidebar\"\n * />\n * ```\n */\nexport function NewsletterCard({\n title = 'Newsletter',\n description = 'Subscribe to get updates.',\n cardClassName,\n titleClassName,\n descriptionClassName,\n ...formProps\n}: NewsletterCardProps) {\n return (\n <div\n className={cn(\n 'rounded-lg border bg-card text-card-foreground shadow-sm',\n 'p-6',\n cardClassName\n )}\n >\n <div className=\"space-y-4\">\n <div className=\"space-y-1.5\">\n <h3\n className={cn(\n 'text-lg font-semibold leading-none tracking-tight',\n titleClassName\n )}\n >\n {title}\n </h3>\n <p\n className={cn(\n 'text-sm text-muted-foreground',\n descriptionClassName\n )}\n >\n {description}\n </p>\n </div>\n <NewsletterForm {...formProps} />\n </div>\n </div>\n );\n}\n\n// ============================================================================\n// Newsletter Inline - Minimal inline variant\n// ============================================================================\n\ninterface NewsletterInlineProps extends NewsletterFormProps {\n label?: string;\n labelClassName?: string;\n}\n\n/**\n * Inline newsletter subscription component\n * \n * @example\n * ```tsx\n * // In a footer\n * <NewsletterInline\n * endpoint=\"/api/newsletter/subscribe\"\n * label=\"Subscribe:\"\n * source=\"footer\"\n * />\n * ```\n */\nexport function NewsletterInline({\n label,\n labelClassName,\n ...formProps\n}: NewsletterInlineProps) {\n return (\n <div className=\"flex flex-col gap-2\">\n {label && (\n <label\n className={cn(\n 'text-sm font-medium leading-none',\n labelClassName\n )}\n >\n {label}\n </label>\n )}\n <NewsletterForm {...formProps} />\n </div>\n );\n}\n\n// ============================================================================\n// Newsletter Modal - Dialog/modal variant (controlled externally)\n// ============================================================================\n\ninterface NewsletterModalContentProps extends NewsletterFormProps {\n title?: string;\n description?: string;\n titleClassName?: string;\n descriptionClassName?: string;\n}\n\n/**\n * Newsletter content for use inside a modal/dialog\n * \n * Use with shadcn/ui Dialog component:\n * \n * @example\n * ```tsx\n * import { Dialog, DialogContent, DialogTrigger } from '@/components/ui/dialog';\n * import { NewsletterModalContent } from '@volchok/newsletter-kit/components';\n * \n * <Dialog>\n * <DialogTrigger asChild>\n * <Button>Subscribe</Button>\n * </DialogTrigger>\n * <DialogContent>\n * <NewsletterModalContent\n * endpoint=\"/api/newsletter/subscribe\"\n * title=\"Join our newsletter\"\n * description=\"Get weekly updates on the latest news.\"\n * source=\"popup\"\n * onSuccess={() => {\n * // Close modal after success\n * setTimeout(() => setOpen(false), 2000);\n * }}\n * />\n * </DialogContent>\n * </Dialog>\n * ```\n */\nexport function NewsletterModalContent({\n title = 'Subscribe to our newsletter',\n description = \"We'll send you the best content, no spam.\",\n titleClassName,\n descriptionClassName,\n ...formProps\n}: NewsletterModalContentProps) {\n return (\n <div className=\"space-y-4\">\n <div className=\"space-y-2 text-center\">\n <h2\n className={cn(\n 'text-lg font-semibold leading-none tracking-tight',\n titleClassName\n )}\n >\n {title}\n </h2>\n <p\n className={cn(\n 'text-sm text-muted-foreground',\n descriptionClassName\n )}\n >\n {description}\n </p>\n </div>\n <NewsletterForm\n {...formProps}\n formClassName=\"flex-col\"\n buttonClassName=\"w-full\"\n />\n </div>\n );\n}\n\n// ============================================================================\n// Newsletter Footer - Optimized for site footers\n// ============================================================================\n\ninterface NewsletterFooterProps extends NewsletterFormProps {\n title?: string;\n description?: string;\n containerClassName?: string;\n titleClassName?: string;\n descriptionClassName?: string;\n privacyText?: string;\n privacyLink?: string;\n}\n\n/**\n * Footer-optimized newsletter subscription component\n * \n * @example\n * ```tsx\n * <footer>\n * <NewsletterFooter\n * endpoint=\"/api/newsletter/subscribe\"\n * title=\"Newsletter\"\n * description=\"Stay up to date with our latest news.\"\n * source=\"footer\"\n * privacyText=\"We respect your privacy.\"\n * privacyLink=\"/privacy\"\n * />\n * </footer>\n * ```\n */\nexport function NewsletterFooter({\n title = 'Newsletter',\n description,\n containerClassName,\n titleClassName,\n descriptionClassName,\n privacyText,\n privacyLink,\n ...formProps\n}: NewsletterFooterProps) {\n return (\n <div className={cn('space-y-4', containerClassName)}>\n {title && (\n <h3\n className={cn(\n 'text-sm font-semibold uppercase tracking-wider',\n titleClassName\n )}\n >\n {title}\n </h3>\n )}\n {description && (\n <p\n className={cn(\n 'text-sm text-muted-foreground',\n descriptionClassName\n )}\n >\n {description}\n </p>\n )}\n <NewsletterForm {...formProps} />\n {privacyText && (\n <p className=\"text-xs text-muted-foreground\">\n {privacyLink ? (\n <>\n {privacyText.replace(/\\.$/, '')}.{' '}\n <a href={privacyLink} className=\"underline hover:text-foreground\">\n Privacy Policy\n </a>\n </>\n ) : (\n privacyText\n )}\n </p>\n )}\n </div>\n );\n}\n","'use client';\n\nimport { useState, useCallback } from 'react';\n\ninterface UseNewsletterOptions {\n endpoint?: string;\n source?: string;\n tags?: string[];\n metadata?: Record<string, unknown>;\n onSuccess?: (email: string, message: string) => void;\n onError?: (error: string) => void;\n}\n\ninterface UseNewsletterState {\n status: 'idle' | 'loading' | 'success' | 'error';\n message: string;\n email: string | null;\n}\n\ninterface UseNewsletterReturn extends UseNewsletterState {\n subscribe: (email: string) => Promise<boolean>;\n reset: () => void;\n isLoading: boolean;\n isSuccess: boolean;\n isError: boolean;\n}\n\n/**\n * React hook for programmatic newsletter subscription\n * \n * @example\n * ```tsx\n * const { subscribe, isLoading, isSuccess, message } = useNewsletter({\n * endpoint: '/api/newsletter/subscribe',\n * source: 'custom-form',\n * onSuccess: (email) => {\n * analytics.track('newsletter_subscribed', { email });\n * },\n * });\n * \n * const handleSubmit = async (email: string) => {\n * const success = await subscribe(email);\n * if (success) {\n * // Show confetti or something\n * }\n * };\n * ```\n */\nexport function useNewsletter(options: UseNewsletterOptions = {}): UseNewsletterReturn {\n const {\n endpoint = '/api/newsletter/subscribe',\n source,\n tags,\n metadata,\n onSuccess,\n onError,\n } = options;\n\n const [state, setState] = useState<UseNewsletterState>({\n status: 'idle',\n message: '',\n email: null,\n });\n\n const subscribe = useCallback(\n async (email: string): Promise<boolean> => {\n if (!email || !email.includes('@')) {\n const errorMsg = 'Please enter a valid email address.';\n setState({\n status: 'error',\n message: errorMsg,\n email,\n });\n onError?.(errorMsg);\n return false;\n }\n\n setState({ status: 'loading', message: '', email });\n\n try {\n const response = await fetch(endpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n email,\n source,\n tags,\n metadata,\n }),\n });\n\n const data = await response.json();\n\n if (response.ok && data.success) {\n const message = data.message || 'Thanks for subscribing!';\n setState({\n status: 'success',\n message,\n email,\n });\n onSuccess?.(email, message);\n return true;\n } else {\n const message = data.message || 'Something went wrong. Please try again.';\n setState({\n status: 'error',\n message,\n email,\n });\n onError?.(message);\n return false;\n }\n } catch (error) {\n const message = 'Network error. Please try again.';\n setState({\n status: 'error',\n message,\n email,\n });\n onError?.(message);\n return false;\n }\n },\n [endpoint, source, tags, metadata, onSuccess, onError]\n );\n\n const reset = useCallback(() => {\n setState({\n status: 'idle',\n message: '',\n email: null,\n });\n }, []);\n\n return {\n ...state,\n subscribe,\n reset,\n isLoading: state.status === 'loading',\n isSuccess: state.status === 'success',\n isError: state.status === 'error',\n };\n}\n"],"mappings":"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","names":["Fragment","jsx","jsxs","useState","useCallback"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { R as RateLimitConfig } from './types-BmajlhNp.js';
|
|
2
|
+
export { A as APIResponse, C as ConfirmResult, E as EmailAdapter, a as EmailAdapterConfig, b as EmailTemplates, N as NewsletterConfig, c as NewsletterFormProps, d as NewsletterFormState, S as StorageAdapter, e as SubscribeInput, f as SubscribeRequest, g as SubscribeResult, h as Subscriber, i as SubscriptionStatus, U as UnsubscribeResult } from './types-BmajlhNp.js';
|
|
3
|
+
export { NewsletterHandlers, createNewsletterHandlers } from './server/index.js';
|
|
4
|
+
export { NewsletterBlock, NewsletterCard, NewsletterFooter, NewsletterForm, NewsletterInline, NewsletterModalContent, useNewsletter } from './components/index.js';
|
|
5
|
+
export { createMailchimpAdapter, createMailchimpStorageAdapter, createNodemailerAdapter, createResendAdapter } from './adapters/email/index.js';
|
|
6
|
+
export { createMemoryAdapter, createNoopAdapter, createPrismaAdapter, createSupabaseAdapter } from './adapters/storage/index.js';
|
|
7
|
+
import 'react/jsx-runtime';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Generate a secure random token for email confirmation
|
|
11
|
+
*/
|
|
12
|
+
declare function generateToken(length?: number): string;
|
|
13
|
+
/**
|
|
14
|
+
* Generate a URL-safe token
|
|
15
|
+
*/
|
|
16
|
+
declare function generateUrlSafeToken(length?: number): string;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Validate email format
|
|
20
|
+
* Uses a reasonable regex that catches most issues without being overly strict
|
|
21
|
+
*/
|
|
22
|
+
declare function isValidEmail(email: string): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Normalize email address
|
|
25
|
+
* - Lowercase
|
|
26
|
+
* - Trim whitespace
|
|
27
|
+
* - Remove dots from Gmail local part (optional)
|
|
28
|
+
*/
|
|
29
|
+
declare function normalizeEmail(email: string, options?: {
|
|
30
|
+
normalizeGmail?: boolean;
|
|
31
|
+
}): string;
|
|
32
|
+
declare function isDisposableEmail(email: string): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Sanitize string input to prevent XSS
|
|
35
|
+
*/
|
|
36
|
+
declare function sanitizeString(input: string): string;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Simple in-memory rate limiter
|
|
40
|
+
*
|
|
41
|
+
* For production with multiple server instances, consider:
|
|
42
|
+
* - Redis-based rate limiting
|
|
43
|
+
* - Upstash Ratelimit
|
|
44
|
+
* - Arcjet
|
|
45
|
+
*/
|
|
46
|
+
declare function createRateLimiter(config: RateLimitConfig): {
|
|
47
|
+
/**
|
|
48
|
+
* Check if request should be rate limited
|
|
49
|
+
* Returns true if request is allowed, false if rate limited
|
|
50
|
+
*/
|
|
51
|
+
check(req: Request): Promise<{
|
|
52
|
+
allowed: boolean;
|
|
53
|
+
remaining: number;
|
|
54
|
+
resetAt: Date;
|
|
55
|
+
}>;
|
|
56
|
+
/**
|
|
57
|
+
* Get current limit status without incrementing
|
|
58
|
+
*/
|
|
59
|
+
status(req: Request): Promise<{
|
|
60
|
+
remaining: number;
|
|
61
|
+
resetAt: Date | null;
|
|
62
|
+
}>;
|
|
63
|
+
/**
|
|
64
|
+
* Reset rate limit for an identifier
|
|
65
|
+
*/
|
|
66
|
+
reset(req: Request): Promise<void>;
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* Extract client IP from request
|
|
70
|
+
*/
|
|
71
|
+
declare function getClientIP(req: Request): string;
|
|
72
|
+
|
|
73
|
+
export { RateLimitConfig, createRateLimiter, generateToken, generateUrlSafeToken, getClientIP, isDisposableEmail, isValidEmail, normalizeEmail, sanitizeString };
|