payload-plugin-newsletter 0.20.1 → 0.20.3

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.
@@ -1,2460 +0,0 @@
1
- "use strict";
2
- "use client";
3
- var __create = Object.create;
4
- var __defProp = Object.defineProperty;
5
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
- var __getOwnPropNames = Object.getOwnPropertyNames;
7
- var __getProtoOf = Object.getPrototypeOf;
8
- var __hasOwnProp = Object.prototype.hasOwnProperty;
9
- var __export = (target, all) => {
10
- for (var name in all)
11
- __defProp(target, name, { get: all[name], enumerable: true });
12
- };
13
- var __copyProps = (to, from, except, desc) => {
14
- if (from && typeof from === "object" || typeof from === "function") {
15
- for (let key of __getOwnPropNames(from))
16
- if (!__hasOwnProp.call(to, key) && key !== except)
17
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
- }
19
- return to;
20
- };
21
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
- // If the importer is in node compatibility mode or this is not an ESM
23
- // file that has been converted to a CommonJS file using a Babel-
24
- // compatible transform (i.e. "__esModule" has not been set), then set
25
- // "default" to the CommonJS "module.exports" for node compatibility.
26
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
- mod
28
- ));
29
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
-
31
- // src/exports/components.ts
32
- var components_exports = {};
33
- __export(components_exports, {
34
- BroadcastEditor: () => BroadcastEditor,
35
- BroadcastInlinePreview: () => BroadcastInlinePreview,
36
- BroadcastPreviewField: () => BroadcastPreviewField,
37
- DefaultBroadcastTemplate: () => DefaultBroadcastTemplate,
38
- EmailPreview: () => EmailPreview,
39
- EmailPreviewField: () => EmailPreviewField,
40
- EmailRenderer: () => EmailRenderer,
41
- EmptyField: () => EmptyField,
42
- MagicLinkVerify: () => MagicLinkVerify,
43
- NewsletterForm: () => NewsletterForm,
44
- PreferencesForm: () => PreferencesForm,
45
- PreviewControls: () => PreviewControls,
46
- StatusBadge: () => StatusBadge,
47
- createMagicLinkVerify: () => createMagicLinkVerify,
48
- createNewsletterForm: () => createNewsletterForm,
49
- createPreferencesForm: () => createPreferencesForm,
50
- useNewsletterAuth: () => useNewsletterAuth
51
- });
52
- module.exports = __toCommonJS(components_exports);
53
-
54
- // src/components/NewsletterForm.tsx
55
- var import_react = require("react");
56
- var import_jsx_runtime = require("react/jsx-runtime");
57
- var defaultStyles = {
58
- form: {
59
- display: "flex",
60
- flexDirection: "column",
61
- gap: "1rem",
62
- maxWidth: "400px",
63
- margin: "0 auto"
64
- },
65
- inputGroup: {
66
- display: "flex",
67
- flexDirection: "column",
68
- gap: "0.5rem"
69
- },
70
- label: {
71
- fontSize: "0.875rem",
72
- fontWeight: "500",
73
- color: "#374151"
74
- },
75
- input: {
76
- padding: "0.5rem 0.75rem",
77
- fontSize: "1rem",
78
- border: "1px solid #e5e7eb",
79
- borderRadius: "0.375rem",
80
- outline: "none",
81
- transition: "border-color 0.2s"
82
- },
83
- button: {
84
- padding: "0.75rem 1.5rem",
85
- fontSize: "1rem",
86
- fontWeight: "500",
87
- color: "#ffffff",
88
- backgroundColor: "#3b82f6",
89
- border: "none",
90
- borderRadius: "0.375rem",
91
- cursor: "pointer",
92
- transition: "background-color 0.2s"
93
- },
94
- buttonDisabled: {
95
- opacity: 0.5,
96
- cursor: "not-allowed"
97
- },
98
- error: {
99
- fontSize: "0.875rem",
100
- color: "#ef4444",
101
- marginTop: "0.25rem"
102
- },
103
- success: {
104
- fontSize: "0.875rem",
105
- color: "#10b981",
106
- marginTop: "0.25rem"
107
- },
108
- checkbox: {
109
- display: "flex",
110
- alignItems: "center",
111
- gap: "0.5rem"
112
- },
113
- checkboxInput: {
114
- width: "1rem",
115
- height: "1rem"
116
- },
117
- checkboxLabel: {
118
- fontSize: "0.875rem",
119
- color: "#374151"
120
- }
121
- };
122
- var NewsletterForm = ({
123
- onSuccess,
124
- onError,
125
- showName = false,
126
- showPreferences = false,
127
- leadMagnet,
128
- className,
129
- styles: customStyles = {},
130
- apiEndpoint = "/api/newsletter/subscribe",
131
- buttonText = "Subscribe",
132
- loadingText = "Subscribing...",
133
- successMessage = "Successfully subscribed!",
134
- placeholders = {
135
- email: "Enter your email",
136
- name: "Enter your name"
137
- },
138
- labels = {
139
- email: "Email",
140
- name: "Name",
141
- newsletter: "Newsletter updates",
142
- announcements: "Product announcements"
143
- }
144
- }) => {
145
- const [email, setEmail] = (0, import_react.useState)("");
146
- const [name, setName] = (0, import_react.useState)("");
147
- const [preferences, setPreferences] = (0, import_react.useState)({
148
- newsletter: true,
149
- announcements: true
150
- });
151
- const [loading, setLoading] = (0, import_react.useState)(false);
152
- const [error, setError] = (0, import_react.useState)(null);
153
- const [success, setSuccess] = (0, import_react.useState)(false);
154
- const styles = {
155
- form: { ...defaultStyles.form, ...customStyles.form },
156
- inputGroup: { ...defaultStyles.inputGroup, ...customStyles.inputGroup },
157
- label: { ...defaultStyles.label, ...customStyles.label },
158
- input: { ...defaultStyles.input, ...customStyles.input },
159
- button: { ...defaultStyles.button, ...customStyles.button },
160
- buttonDisabled: { ...defaultStyles.buttonDisabled, ...customStyles.buttonDisabled },
161
- error: { ...defaultStyles.error, ...customStyles.error },
162
- success: { ...defaultStyles.success, ...customStyles.success },
163
- checkbox: { ...defaultStyles.checkbox, ...customStyles.checkbox },
164
- checkboxInput: { ...defaultStyles.checkboxInput, ...customStyles.checkboxInput },
165
- checkboxLabel: { ...defaultStyles.checkboxLabel, ...customStyles.checkboxLabel }
166
- };
167
- const handleSubmit = async (e) => {
168
- e.preventDefault();
169
- setError(null);
170
- setLoading(true);
171
- try {
172
- const payload = {
173
- email,
174
- ...showName && name && { name },
175
- ...showPreferences && { preferences },
176
- ...leadMagnet && { leadMagnet: leadMagnet.id },
177
- metadata: {
178
- signupPage: window.location.href,
179
- ...typeof window !== "undefined" && window.location.search && {
180
- utmParams: Object.fromEntries(new URLSearchParams(window.location.search))
181
- }
182
- }
183
- };
184
- const response = await fetch(apiEndpoint, {
185
- method: "POST",
186
- headers: {
187
- "Content-Type": "application/json"
188
- },
189
- body: JSON.stringify(payload)
190
- });
191
- const data = await response.json();
192
- if (!response.ok) {
193
- throw new Error(data.error || data.errors?.join(", ") || "Subscription failed");
194
- }
195
- setSuccess(true);
196
- setEmail("");
197
- setName("");
198
- if (onSuccess) {
199
- onSuccess(data.subscriber);
200
- }
201
- } catch (err) {
202
- const errorMessage = err instanceof Error ? err.message : "An error occurred";
203
- setError(errorMessage);
204
- if (onError) {
205
- onError(new Error(errorMessage));
206
- }
207
- } finally {
208
- setLoading(false);
209
- }
210
- };
211
- if (success && !showPreferences) {
212
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className, style: styles.form, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: styles.success, children: successMessage }) });
213
- }
214
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", { onSubmit: handleSubmit, className, style: styles.form, children: [
215
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.inputGroup, children: [
216
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { htmlFor: "email", style: styles.label, children: labels.email }),
217
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
218
- "input",
219
- {
220
- id: "email",
221
- type: "email",
222
- value: email,
223
- onChange: (e) => setEmail(e.target.value),
224
- placeholder: placeholders.email,
225
- required: true,
226
- disabled: loading,
227
- style: {
228
- ...styles.input,
229
- ...loading && { opacity: 0.5 }
230
- }
231
- }
232
- )
233
- ] }),
234
- showName && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.inputGroup, children: [
235
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { htmlFor: "name", style: styles.label, children: labels.name }),
236
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
237
- "input",
238
- {
239
- id: "name",
240
- type: "text",
241
- value: name,
242
- onChange: (e) => setName(e.target.value),
243
- placeholder: placeholders.name,
244
- disabled: loading,
245
- style: {
246
- ...styles.input,
247
- ...loading && { opacity: 0.5 }
248
- }
249
- }
250
- )
251
- ] }),
252
- showPreferences && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.inputGroup, children: [
253
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { style: styles.label, children: "Email Preferences" }),
254
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.checkbox, children: [
255
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
256
- "input",
257
- {
258
- id: "newsletter",
259
- type: "checkbox",
260
- checked: preferences.newsletter,
261
- onChange: (e) => setPreferences({ ...preferences, newsletter: e.target.checked }),
262
- disabled: loading,
263
- style: styles.checkboxInput
264
- }
265
- ),
266
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { htmlFor: "newsletter", style: styles.checkboxLabel, children: labels.newsletter })
267
- ] }),
268
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.checkbox, children: [
269
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
270
- "input",
271
- {
272
- id: "announcements",
273
- type: "checkbox",
274
- checked: preferences.announcements,
275
- onChange: (e) => setPreferences({ ...preferences, announcements: e.target.checked }),
276
- disabled: loading,
277
- style: styles.checkboxInput
278
- }
279
- ),
280
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { htmlFor: "announcements", style: styles.checkboxLabel, children: labels.announcements })
281
- ] })
282
- ] }),
283
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
284
- "button",
285
- {
286
- type: "submit",
287
- disabled: loading,
288
- style: {
289
- ...styles.button,
290
- ...loading && styles.buttonDisabled
291
- },
292
- children: loading ? loadingText : buttonText
293
- }
294
- ),
295
- error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: styles.error, children: error }),
296
- success && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: styles.success, children: successMessage })
297
- ] });
298
- };
299
- function createNewsletterForm(defaultProps) {
300
- return (props) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(NewsletterForm, { ...defaultProps, ...props });
301
- }
302
-
303
- // src/components/PreferencesForm.tsx
304
- var import_react2 = require("react");
305
- var import_jsx_runtime2 = require("react/jsx-runtime");
306
- var defaultStyles2 = {
307
- container: {
308
- maxWidth: "600px",
309
- margin: "0 auto",
310
- padding: "2rem"
311
- },
312
- heading: {
313
- fontSize: "1.5rem",
314
- fontWeight: "600",
315
- marginBottom: "1.5rem",
316
- color: "#111827"
317
- },
318
- form: {
319
- display: "flex",
320
- flexDirection: "column",
321
- gap: "1.5rem"
322
- },
323
- section: {
324
- padding: "1.5rem",
325
- backgroundColor: "#f9fafb",
326
- borderRadius: "0.5rem",
327
- border: "1px solid #e5e7eb"
328
- },
329
- sectionTitle: {
330
- fontSize: "1.125rem",
331
- fontWeight: "500",
332
- marginBottom: "1rem",
333
- color: "#111827"
334
- },
335
- inputGroup: {
336
- display: "flex",
337
- flexDirection: "column",
338
- gap: "0.5rem"
339
- },
340
- label: {
341
- fontSize: "0.875rem",
342
- fontWeight: "500",
343
- color: "#374151"
344
- },
345
- input: {
346
- padding: "0.5rem 0.75rem",
347
- fontSize: "1rem",
348
- border: "1px solid #e5e7eb",
349
- borderRadius: "0.375rem",
350
- outline: "none",
351
- transition: "border-color 0.2s"
352
- },
353
- select: {
354
- padding: "0.5rem 0.75rem",
355
- fontSize: "1rem",
356
- border: "1px solid #e5e7eb",
357
- borderRadius: "0.375rem",
358
- outline: "none",
359
- backgroundColor: "#ffffff"
360
- },
361
- checkbox: {
362
- display: "flex",
363
- alignItems: "center",
364
- gap: "0.5rem",
365
- marginBottom: "0.5rem"
366
- },
367
- checkboxInput: {
368
- width: "1rem",
369
- height: "1rem"
370
- },
371
- checkboxLabel: {
372
- fontSize: "0.875rem",
373
- color: "#374151"
374
- },
375
- buttonGroup: {
376
- display: "flex",
377
- gap: "1rem",
378
- marginTop: "1rem"
379
- },
380
- button: {
381
- padding: "0.75rem 1.5rem",
382
- fontSize: "1rem",
383
- fontWeight: "500",
384
- borderRadius: "0.375rem",
385
- cursor: "pointer",
386
- transition: "all 0.2s",
387
- border: "none"
388
- },
389
- primaryButton: {
390
- color: "#ffffff",
391
- backgroundColor: "#3b82f6"
392
- },
393
- secondaryButton: {
394
- color: "#374151",
395
- backgroundColor: "#ffffff",
396
- border: "1px solid #e5e7eb"
397
- },
398
- dangerButton: {
399
- color: "#ffffff",
400
- backgroundColor: "#ef4444"
401
- },
402
- error: {
403
- fontSize: "0.875rem",
404
- color: "#ef4444",
405
- marginTop: "0.5rem"
406
- },
407
- success: {
408
- fontSize: "0.875rem",
409
- color: "#10b981",
410
- marginTop: "0.5rem"
411
- },
412
- info: {
413
- fontSize: "0.875rem",
414
- color: "#6b7280",
415
- marginTop: "0.5rem"
416
- }
417
- };
418
- var PreferencesForm = ({
419
- subscriber: initialSubscriber,
420
- onSuccess,
421
- onError,
422
- className,
423
- styles: customStyles = {},
424
- sessionToken,
425
- apiEndpoint = "/api/newsletter/preferences",
426
- showUnsubscribe = true,
427
- locales = ["en"],
428
- labels = {
429
- title: "Newsletter Preferences",
430
- personalInfo: "Personal Information",
431
- emailPreferences: "Email Preferences",
432
- name: "Name",
433
- language: "Preferred Language",
434
- newsletter: "Newsletter updates",
435
- announcements: "Product announcements",
436
- saveButton: "Save Preferences",
437
- unsubscribeButton: "Unsubscribe",
438
- saving: "Saving...",
439
- saved: "Preferences saved successfully!",
440
- unsubscribeConfirm: "Are you sure you want to unsubscribe? This cannot be undone."
441
- }
442
- }) => {
443
- const [subscriber, setSubscriber] = (0, import_react2.useState)(initialSubscriber || {});
444
- const [loading, setLoading] = (0, import_react2.useState)(false);
445
- const [loadingData, setLoadingData] = (0, import_react2.useState)(!initialSubscriber);
446
- const [error, setError] = (0, import_react2.useState)(null);
447
- const [success, setSuccess] = (0, import_react2.useState)(false);
448
- const styles = {
449
- container: { ...defaultStyles2.container, ...customStyles.container },
450
- heading: { ...defaultStyles2.heading, ...customStyles.heading },
451
- form: { ...defaultStyles2.form, ...customStyles.form },
452
- section: { ...defaultStyles2.section, ...customStyles.section },
453
- sectionTitle: { ...defaultStyles2.sectionTitle, ...customStyles.sectionTitle },
454
- inputGroup: { ...defaultStyles2.inputGroup, ...customStyles.inputGroup },
455
- label: { ...defaultStyles2.label, ...customStyles.label },
456
- input: { ...defaultStyles2.input, ...customStyles.input },
457
- select: { ...defaultStyles2.select, ...customStyles.select },
458
- checkbox: { ...defaultStyles2.checkbox, ...customStyles.checkbox },
459
- checkboxInput: { ...defaultStyles2.checkboxInput, ...customStyles.checkboxInput },
460
- checkboxLabel: { ...defaultStyles2.checkboxLabel, ...customStyles.checkboxLabel },
461
- buttonGroup: { ...defaultStyles2.buttonGroup, ...customStyles.buttonGroup },
462
- button: { ...defaultStyles2.button, ...customStyles.button },
463
- primaryButton: { ...defaultStyles2.primaryButton, ...customStyles.primaryButton },
464
- secondaryButton: { ...defaultStyles2.secondaryButton, ...customStyles.secondaryButton },
465
- dangerButton: { ...defaultStyles2.dangerButton, ...customStyles.dangerButton },
466
- error: { ...defaultStyles2.error, ...customStyles.error },
467
- success: { ...defaultStyles2.success, ...customStyles.success },
468
- info: { ...defaultStyles2.info, ...customStyles.info }
469
- };
470
- (0, import_react2.useEffect)(() => {
471
- if (!initialSubscriber && sessionToken) {
472
- fetchPreferences();
473
- }
474
- }, []);
475
- const fetchPreferences = async () => {
476
- try {
477
- const response = await fetch(apiEndpoint, {
478
- headers: {
479
- "Authorization": `Bearer ${sessionToken}`
480
- }
481
- });
482
- if (!response.ok) {
483
- throw new Error("Failed to load preferences");
484
- }
485
- const data = await response.json();
486
- setSubscriber(data.subscriber);
487
- } catch (err) {
488
- setError(err instanceof Error ? err.message : "Failed to load preferences");
489
- if (onError) {
490
- onError(err instanceof Error ? err : new Error("Failed to load preferences"));
491
- }
492
- } finally {
493
- setLoadingData(false);
494
- }
495
- };
496
- const handleSave = async (e) => {
497
- e.preventDefault();
498
- setError(null);
499
- setSuccess(false);
500
- setLoading(true);
501
- try {
502
- const response = await fetch(apiEndpoint, {
503
- method: "POST",
504
- headers: {
505
- "Content-Type": "application/json",
506
- "Authorization": `Bearer ${sessionToken}`
507
- },
508
- body: JSON.stringify({
509
- name: subscriber.name,
510
- locale: subscriber.locale,
511
- emailPreferences: subscriber.emailPreferences
512
- })
513
- });
514
- const data = await response.json();
515
- if (!response.ok) {
516
- throw new Error(data.error || "Failed to save preferences");
517
- }
518
- setSubscriber(data.subscriber);
519
- setSuccess(true);
520
- if (onSuccess) {
521
- onSuccess(data.subscriber);
522
- }
523
- } catch (err) {
524
- const errorMessage = err instanceof Error ? err.message : "An error occurred";
525
- setError(errorMessage);
526
- if (onError) {
527
- onError(new Error(errorMessage));
528
- }
529
- } finally {
530
- setLoading(false);
531
- }
532
- };
533
- const handleUnsubscribe = async () => {
534
- if (!window.confirm(labels.unsubscribeConfirm)) {
535
- return;
536
- }
537
- setLoading(true);
538
- setError(null);
539
- try {
540
- const response = await fetch("/api/newsletter/unsubscribe", {
541
- method: "POST",
542
- headers: {
543
- "Content-Type": "application/json",
544
- "Authorization": `Bearer ${sessionToken}`
545
- },
546
- body: JSON.stringify({
547
- email: subscriber.email
548
- })
549
- });
550
- if (!response.ok) {
551
- throw new Error("Failed to unsubscribe");
552
- }
553
- setSubscriber({ ...subscriber, subscriptionStatus: "unsubscribed" });
554
- if (onSuccess) {
555
- onSuccess({ ...subscriber, subscriptionStatus: "unsubscribed" });
556
- }
557
- } catch (err) {
558
- setError("Failed to unsubscribe. Please try again.");
559
- if (onError) {
560
- onError(err instanceof Error ? err : new Error("Failed to unsubscribe"));
561
- }
562
- } finally {
563
- setLoading(false);
564
- }
565
- };
566
- if (loadingData) {
567
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className, style: styles.container, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: styles.info, children: "Loading preferences..." }) });
568
- }
569
- if (subscriber.subscriptionStatus === "unsubscribed") {
570
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className, style: styles.container, children: [
571
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h2", { style: styles.heading, children: "Unsubscribed" }),
572
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: styles.info, children: "You have been unsubscribed from all emails. To resubscribe, please sign up again." })
573
- ] });
574
- }
575
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className, style: styles.container, children: [
576
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h2", { style: styles.heading, children: labels.title }),
577
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("form", { onSubmit: handleSave, style: styles.form, children: [
578
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: styles.section, children: [
579
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h3", { style: styles.sectionTitle, children: labels.personalInfo }),
580
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: styles.inputGroup, children: [
581
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { htmlFor: "name", style: styles.label, children: labels.name }),
582
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
583
- "input",
584
- {
585
- id: "name",
586
- type: "text",
587
- value: subscriber.name || "",
588
- onChange: (e) => setSubscriber({ ...subscriber, name: e.target.value }),
589
- disabled: loading,
590
- style: styles.input
591
- }
592
- )
593
- ] }),
594
- locales.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: styles.inputGroup, children: [
595
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { htmlFor: "locale", style: styles.label, children: labels.language }),
596
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
597
- "select",
598
- {
599
- id: "locale",
600
- value: subscriber.locale || locales[0],
601
- onChange: (e) => setSubscriber({ ...subscriber, locale: e.target.value }),
602
- disabled: loading,
603
- style: styles.select,
604
- children: locales.map((locale) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: locale, children: locale.toUpperCase() }, locale))
605
- }
606
- )
607
- ] })
608
- ] }),
609
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: styles.section, children: [
610
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h3", { style: styles.sectionTitle, children: labels.emailPreferences }),
611
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: styles.checkbox, children: [
612
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
613
- "input",
614
- {
615
- id: "pref-newsletter",
616
- type: "checkbox",
617
- checked: subscriber.emailPreferences?.newsletter ?? true,
618
- onChange: (e) => setSubscriber({
619
- ...subscriber,
620
- emailPreferences: {
621
- ...subscriber.emailPreferences,
622
- newsletter: e.target.checked
623
- }
624
- }),
625
- disabled: loading,
626
- style: styles.checkboxInput
627
- }
628
- ),
629
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { htmlFor: "pref-newsletter", style: styles.checkboxLabel, children: labels.newsletter })
630
- ] }),
631
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: styles.checkbox, children: [
632
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
633
- "input",
634
- {
635
- id: "pref-announcements",
636
- type: "checkbox",
637
- checked: subscriber.emailPreferences?.announcements ?? true,
638
- onChange: (e) => setSubscriber({
639
- ...subscriber,
640
- emailPreferences: {
641
- ...subscriber.emailPreferences,
642
- announcements: e.target.checked
643
- }
644
- }),
645
- disabled: loading,
646
- style: styles.checkboxInput
647
- }
648
- ),
649
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { htmlFor: "pref-announcements", style: styles.checkboxLabel, children: labels.announcements })
650
- ] })
651
- ] }),
652
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: styles.buttonGroup, children: [
653
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
654
- "button",
655
- {
656
- type: "submit",
657
- disabled: loading,
658
- style: {
659
- ...styles.button,
660
- ...styles.primaryButton,
661
- ...loading && { opacity: 0.5, cursor: "not-allowed" }
662
- },
663
- children: loading ? labels.saving : labels.saveButton
664
- }
665
- ),
666
- showUnsubscribe && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
667
- "button",
668
- {
669
- type: "button",
670
- onClick: handleUnsubscribe,
671
- disabled: loading,
672
- style: {
673
- ...styles.button,
674
- ...styles.dangerButton,
675
- ...loading && { opacity: 0.5, cursor: "not-allowed" }
676
- },
677
- children: labels.unsubscribeButton
678
- }
679
- )
680
- ] }),
681
- error && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: styles.error, children: error }),
682
- success && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: styles.success, children: labels.saved })
683
- ] })
684
- ] });
685
- };
686
- function createPreferencesForm(defaultProps) {
687
- return (props) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(PreferencesForm, { ...defaultProps, ...props });
688
- }
689
-
690
- // src/components/MagicLinkVerify.tsx
691
- var import_react3 = require("react");
692
- var import_jsx_runtime3 = require("react/jsx-runtime");
693
- var defaultStyles3 = {
694
- container: {
695
- maxWidth: "400px",
696
- margin: "4rem auto",
697
- padding: "2rem",
698
- textAlign: "center"
699
- },
700
- heading: {
701
- fontSize: "1.5rem",
702
- fontWeight: "600",
703
- marginBottom: "1rem",
704
- color: "#111827"
705
- },
706
- message: {
707
- fontSize: "1rem",
708
- color: "#6b7280",
709
- marginBottom: "1.5rem"
710
- },
711
- error: {
712
- fontSize: "1rem",
713
- color: "#ef4444",
714
- marginBottom: "1.5rem"
715
- },
716
- button: {
717
- padding: "0.75rem 1.5rem",
718
- fontSize: "1rem",
719
- fontWeight: "500",
720
- color: "#ffffff",
721
- backgroundColor: "#3b82f6",
722
- border: "none",
723
- borderRadius: "0.375rem",
724
- cursor: "pointer",
725
- transition: "background-color 0.2s"
726
- }
727
- };
728
- var MagicLinkVerify = ({
729
- token: propToken,
730
- onSuccess,
731
- onError,
732
- apiEndpoint = "/api/newsletter/verify-magic-link",
733
- className,
734
- styles: customStyles = {},
735
- labels = {
736
- verifying: "Verifying your magic link...",
737
- success: "Successfully verified! Redirecting...",
738
- error: "Failed to verify magic link",
739
- expired: "This magic link has expired. Please request a new one.",
740
- invalid: "This magic link is invalid. Please request a new one.",
741
- redirecting: "Redirecting to your preferences...",
742
- tryAgain: "Try Again"
743
- }
744
- }) => {
745
- const [status, setStatus] = (0, import_react3.useState)("verifying");
746
- const [error, setError] = (0, import_react3.useState)(null);
747
- const [_sessionToken, setSessionToken] = (0, import_react3.useState)(null);
748
- const styles = {
749
- container: { ...defaultStyles3.container, ...customStyles.container },
750
- heading: { ...defaultStyles3.heading, ...customStyles.heading },
751
- message: { ...defaultStyles3.message, ...customStyles.message },
752
- error: { ...defaultStyles3.error, ...customStyles.error },
753
- button: { ...defaultStyles3.button, ...customStyles.button }
754
- };
755
- (0, import_react3.useEffect)(() => {
756
- const token = propToken || new URLSearchParams(window.location.search).get("token");
757
- if (token) {
758
- verifyToken(token);
759
- } else {
760
- setStatus("error");
761
- setError(labels.invalid || "Invalid magic link");
762
- }
763
- }, [propToken]);
764
- const verifyToken = async (token) => {
765
- try {
766
- const response = await fetch(apiEndpoint, {
767
- method: "POST",
768
- headers: {
769
- "Content-Type": "application/json"
770
- },
771
- body: JSON.stringify({ token })
772
- });
773
- const data = await response.json();
774
- if (!response.ok) {
775
- if (data.error?.includes("expired")) {
776
- throw new Error(labels.expired);
777
- }
778
- throw new Error(data.error || labels.error);
779
- }
780
- setStatus("success");
781
- setSessionToken(data.sessionToken);
782
- if (typeof window !== "undefined" && data.sessionToken) {
783
- localStorage.setItem("newsletter_session", data.sessionToken);
784
- }
785
- if (onSuccess) {
786
- onSuccess(data.sessionToken, data.subscriber);
787
- }
788
- } catch (err) {
789
- setStatus("error");
790
- const errorMessage = err instanceof Error ? err.message : labels.error || "Verification failed";
791
- setError(errorMessage);
792
- if (onError) {
793
- onError(err instanceof Error ? err : new Error(errorMessage));
794
- }
795
- }
796
- };
797
- const handleTryAgain = () => {
798
- window.location.href = "/";
799
- };
800
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className, style: styles.container, children: [
801
- status === "verifying" && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
802
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h2", { style: styles.heading, children: "Verifying" }),
803
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { style: styles.message, children: labels.verifying })
804
- ] }),
805
- status === "success" && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
806
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h2", { style: styles.heading, children: "Success!" }),
807
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { style: styles.message, children: labels.success })
808
- ] }),
809
- status === "error" && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
810
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h2", { style: styles.heading, children: "Verification Failed" }),
811
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { style: styles.error, children: error }),
812
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { onClick: handleTryAgain, style: styles.button, children: labels.tryAgain })
813
- ] })
814
- ] });
815
- };
816
- function createMagicLinkVerify(defaultProps) {
817
- return (props) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MagicLinkVerify, { ...defaultProps, ...props });
818
- }
819
-
820
- // src/hooks/useNewsletterAuth.ts
821
- var import_react4 = require("react");
822
- function useNewsletterAuth(_options = {}) {
823
- const [subscriber, setSubscriber] = (0, import_react4.useState)(null);
824
- const [isLoading, setIsLoading] = (0, import_react4.useState)(true);
825
- const [error, setError] = (0, import_react4.useState)(null);
826
- const checkAuth = (0, import_react4.useCallback)(async () => {
827
- try {
828
- const response = await fetch("/api/newsletter/me", {
829
- method: "GET",
830
- credentials: "include",
831
- headers: {
832
- "Content-Type": "application/json"
833
- }
834
- });
835
- if (response.ok) {
836
- const data = await response.json();
837
- setSubscriber(data.subscriber);
838
- setError(null);
839
- } else {
840
- setSubscriber(null);
841
- if (response.status !== 401) {
842
- setError(new Error("Failed to check authentication"));
843
- }
844
- }
845
- } catch (err) {
846
- console.error("Auth check failed:", err);
847
- setError(err instanceof Error ? err : new Error("An error occurred"));
848
- setSubscriber(null);
849
- } finally {
850
- setIsLoading(false);
851
- }
852
- }, []);
853
- (0, import_react4.useEffect)(() => {
854
- checkAuth();
855
- }, [checkAuth]);
856
- const signOut = (0, import_react4.useCallback)(async () => {
857
- try {
858
- const response = await fetch("/api/newsletter/signout", {
859
- method: "POST",
860
- credentials: "include",
861
- headers: {
862
- "Content-Type": "application/json"
863
- }
864
- });
865
- if (response.ok) {
866
- setSubscriber(null);
867
- setError(null);
868
- } else {
869
- throw new Error("Failed to sign out");
870
- }
871
- } catch (err) {
872
- console.error("Sign out error:", err);
873
- setError(err instanceof Error ? err : new Error("Sign out failed"));
874
- throw err;
875
- }
876
- }, []);
877
- const refreshAuth = (0, import_react4.useCallback)(async () => {
878
- setIsLoading(true);
879
- await checkAuth();
880
- }, [checkAuth]);
881
- const login = (0, import_react4.useCallback)(async (_token) => {
882
- await refreshAuth();
883
- }, [refreshAuth]);
884
- return {
885
- subscriber,
886
- isAuthenticated: !!subscriber,
887
- isLoading,
888
- loading: isLoading,
889
- // Alias for backward compatibility
890
- error,
891
- signOut,
892
- logout: signOut,
893
- // Alias for backward compatibility
894
- refreshAuth,
895
- refreshSubscriber: refreshAuth,
896
- // Alias for backward compatibility
897
- login
898
- // For backward compatibility
899
- };
900
- }
901
-
902
- // src/components/Broadcasts/EmailPreview.tsx
903
- var import_react6 = require("react");
904
-
905
- // src/utils/emailSafeHtml.ts
906
- var import_isomorphic_dompurify = __toESM(require("isomorphic-dompurify"), 1);
907
- var EMAIL_SAFE_CONFIG = {
908
- ALLOWED_TAGS: [
909
- "p",
910
- "br",
911
- "strong",
912
- "b",
913
- "em",
914
- "i",
915
- "u",
916
- "strike",
917
- "s",
918
- "span",
919
- "a",
920
- "h1",
921
- "h2",
922
- "h3",
923
- "ul",
924
- "ol",
925
- "li",
926
- "blockquote",
927
- "hr",
928
- "img",
929
- "div",
930
- "table",
931
- "tr",
932
- "td",
933
- "th",
934
- "tbody",
935
- "thead"
936
- ],
937
- ALLOWED_ATTR: ["href", "style", "target", "rel", "align", "src", "alt", "width", "height", "border", "cellpadding", "cellspacing"],
938
- ALLOWED_STYLES: {
939
- "*": [
940
- "color",
941
- "background-color",
942
- "font-size",
943
- "font-weight",
944
- "font-style",
945
- "text-decoration",
946
- "text-align",
947
- "margin",
948
- "margin-top",
949
- "margin-right",
950
- "margin-bottom",
951
- "margin-left",
952
- "padding",
953
- "padding-top",
954
- "padding-right",
955
- "padding-bottom",
956
- "padding-left",
957
- "line-height",
958
- "border-left",
959
- "border-left-width",
960
- "border-left-style",
961
- "border-left-color"
962
- ]
963
- },
964
- FORBID_TAGS: ["script", "style", "iframe", "object", "embed", "form", "input"],
965
- FORBID_ATTR: ["class", "id", "onclick", "onload", "onerror"]
966
- };
967
- async function convertToEmailSafeHtml(editorState, options) {
968
- if (!editorState) {
969
- return "";
970
- }
971
- const rawHtml = await lexicalToEmailHtml(editorState, options?.mediaUrl, options?.customBlockConverter);
972
- const sanitizedHtml = import_isomorphic_dompurify.default.sanitize(rawHtml, EMAIL_SAFE_CONFIG);
973
- if (options?.wrapInTemplate) {
974
- if (options.customWrapper) {
975
- return await Promise.resolve(options.customWrapper(sanitizedHtml, {
976
- preheader: options.preheader,
977
- subject: options.subject
978
- }));
979
- }
980
- return wrapInEmailTemplate(sanitizedHtml, options.preheader);
981
- }
982
- return sanitizedHtml;
983
- }
984
- async function lexicalToEmailHtml(editorState, mediaUrl, customBlockConverter) {
985
- const { root } = editorState;
986
- if (!root || !root.children) {
987
- return "";
988
- }
989
- const htmlParts = await Promise.all(
990
- root.children.map((node) => convertNode(node, mediaUrl, customBlockConverter))
991
- );
992
- return htmlParts.join("");
993
- }
994
- async function convertNode(node, mediaUrl, customBlockConverter) {
995
- switch (node.type) {
996
- case "paragraph":
997
- return convertParagraph(node, mediaUrl, customBlockConverter);
998
- case "heading":
999
- return convertHeading(node, mediaUrl, customBlockConverter);
1000
- case "list":
1001
- return convertList(node, mediaUrl, customBlockConverter);
1002
- case "listitem":
1003
- return convertListItem(node, mediaUrl, customBlockConverter);
1004
- case "blockquote":
1005
- return convertBlockquote(node, mediaUrl, customBlockConverter);
1006
- case "text":
1007
- return convertText(node);
1008
- case "link":
1009
- return convertLink(node, mediaUrl, customBlockConverter);
1010
- case "linebreak":
1011
- return "<br>";
1012
- case "upload":
1013
- return convertUpload(node, mediaUrl);
1014
- case "block":
1015
- return await convertBlock(node, mediaUrl, customBlockConverter);
1016
- default:
1017
- if (node.children) {
1018
- const childParts = await Promise.all(
1019
- node.children.map((child) => convertNode(child, mediaUrl, customBlockConverter))
1020
- );
1021
- return childParts.join("");
1022
- }
1023
- return "";
1024
- }
1025
- }
1026
- async function convertParagraph(node, mediaUrl, customBlockConverter) {
1027
- const align = getAlignment(node.format);
1028
- const childParts = await Promise.all(
1029
- (node.children || []).map((child) => convertNode(child, mediaUrl, customBlockConverter))
1030
- );
1031
- const children = childParts.join("");
1032
- if (!children.trim()) {
1033
- return '<p class="mobile-margin-bottom-16" style="margin: 0 0 16px 0; min-height: 1em;">&nbsp;</p>';
1034
- }
1035
- return `<p class="mobile-margin-bottom-16" style="margin: 0 0 16px 0; text-align: ${align}; font-size: 16px; line-height: 1.5;">${children}</p>`;
1036
- }
1037
- async function convertHeading(node, mediaUrl, customBlockConverter) {
1038
- const tag = node.tag || "h1";
1039
- const align = getAlignment(node.format);
1040
- const childParts = await Promise.all(
1041
- (node.children || []).map((child) => convertNode(child, mediaUrl, customBlockConverter))
1042
- );
1043
- const children = childParts.join("");
1044
- const styles = {
1045
- h1: "font-size: 32px; font-weight: 700; margin: 0 0 24px 0; line-height: 1.2;",
1046
- h2: "font-size: 24px; font-weight: 600; margin: 0 0 16px 0; line-height: 1.3;",
1047
- h3: "font-size: 20px; font-weight: 600; margin: 0 0 12px 0; line-height: 1.4;"
1048
- };
1049
- const mobileClasses = {
1050
- h1: "mobile-font-size-24",
1051
- h2: "mobile-font-size-20",
1052
- h3: "mobile-font-size-16"
1053
- };
1054
- const style = `${styles[tag] || styles.h3} text-align: ${align};`;
1055
- const mobileClass = mobileClasses[tag] || mobileClasses.h3;
1056
- return `<${tag} class="${mobileClass}" style="${style}">${children}</${tag}>`;
1057
- }
1058
- async function convertList(node, mediaUrl, customBlockConverter) {
1059
- const tag = node.listType === "number" ? "ol" : "ul";
1060
- const childParts = await Promise.all(
1061
- (node.children || []).map((child) => convertNode(child, mediaUrl, customBlockConverter))
1062
- );
1063
- const children = childParts.join("");
1064
- const style = tag === "ul" ? "margin: 0 0 16px 0; padding-left: 24px; list-style-type: disc; font-size: 16px; line-height: 1.5;" : "margin: 0 0 16px 0; padding-left: 24px; list-style-type: decimal; font-size: 16px; line-height: 1.5;";
1065
- return `<${tag} class="mobile-margin-bottom-16" style="${style}">${children}</${tag}>`;
1066
- }
1067
- async function convertListItem(node, mediaUrl, customBlockConverter) {
1068
- const childParts = await Promise.all(
1069
- (node.children || []).map((child) => convertNode(child, mediaUrl, customBlockConverter))
1070
- );
1071
- const children = childParts.join("");
1072
- return `<li style="margin: 0 0 8px 0;">${children}</li>`;
1073
- }
1074
- async function convertBlockquote(node, mediaUrl, customBlockConverter) {
1075
- const childParts = await Promise.all(
1076
- (node.children || []).map((child) => convertNode(child, mediaUrl, customBlockConverter))
1077
- );
1078
- const children = childParts.join("");
1079
- const style = "margin: 0 0 16px 0; padding-left: 16px; border-left: 4px solid #e5e7eb; color: #6b7280;";
1080
- return `<blockquote style="${style}">${children}</blockquote>`;
1081
- }
1082
- function convertText(node) {
1083
- let text = escapeHtml(node.text || "");
1084
- if (node.format & 1) {
1085
- text = `<strong>${text}</strong>`;
1086
- }
1087
- if (node.format & 2) {
1088
- text = `<em>${text}</em>`;
1089
- }
1090
- if (node.format & 8) {
1091
- text = `<u>${text}</u>`;
1092
- }
1093
- if (node.format & 4) {
1094
- text = `<strike>${text}</strike>`;
1095
- }
1096
- return text;
1097
- }
1098
- async function convertLink(node, mediaUrl, customBlockConverter) {
1099
- const childParts = await Promise.all(
1100
- (node.children || []).map((child) => convertNode(child, mediaUrl, customBlockConverter))
1101
- );
1102
- const children = childParts.join("");
1103
- const url = node.fields?.url || "#";
1104
- const newTab = node.fields?.newTab ?? false;
1105
- const targetAttr = newTab ? ' target="_blank"' : "";
1106
- const relAttr = newTab ? ' rel="noopener noreferrer"' : "";
1107
- return `<a href="${escapeHtml(url)}"${targetAttr}${relAttr} style="color: #2563eb; text-decoration: underline;">${children}</a>`;
1108
- }
1109
- function convertUpload(node, mediaUrl) {
1110
- const upload = node.value;
1111
- if (!upload) return "";
1112
- let src = "";
1113
- if (typeof upload === "string") {
1114
- src = upload;
1115
- } else if (upload.url) {
1116
- src = upload.url;
1117
- } else if (upload.filename && mediaUrl) {
1118
- src = `${mediaUrl}/${upload.filename}`;
1119
- }
1120
- const alt = node.fields?.altText || upload.alt || "";
1121
- const caption = node.fields?.caption || "";
1122
- const imgHtml = `<img src="${escapeHtml(src)}" alt="${escapeHtml(alt)}" class="mobile-width-100" style="max-width: 100%; height: auto; display: block; margin: 0 auto; border-radius: 6px;" />`;
1123
- if (caption) {
1124
- return `
1125
- <div style="margin: 0 0 16px 0; text-align: center;" class="mobile-margin-bottom-16">
1126
- ${imgHtml}
1127
- <p style="margin: 8px 0 0 0; font-size: 14px; color: #6b7280; font-style: italic; text-align: center;" class="mobile-font-size-14">${escapeHtml(caption)}</p>
1128
- </div>
1129
- `;
1130
- }
1131
- return `<div style="margin: 0 0 16px 0; text-align: center;" class="mobile-margin-bottom-16">${imgHtml}</div>`;
1132
- }
1133
- async function convertBlock(node, mediaUrl, customBlockConverter) {
1134
- const blockType = node.fields?.blockName || node.blockName;
1135
- if (customBlockConverter) {
1136
- try {
1137
- const customHtml = await customBlockConverter(node, mediaUrl);
1138
- if (customHtml) {
1139
- return customHtml;
1140
- }
1141
- } catch (error) {
1142
- console.error(`Custom block converter error for ${blockType}:`, error);
1143
- }
1144
- }
1145
- switch (blockType) {
1146
- case "button":
1147
- return convertButtonBlock(node.fields);
1148
- case "divider":
1149
- return convertDividerBlock(node.fields);
1150
- default:
1151
- if (node.children) {
1152
- const childParts = await Promise.all(
1153
- node.children.map((child) => convertNode(child, mediaUrl, customBlockConverter))
1154
- );
1155
- return childParts.join("");
1156
- }
1157
- return "";
1158
- }
1159
- }
1160
- function convertButtonBlock(fields) {
1161
- const text = fields?.text || "Click here";
1162
- const url = fields?.url || "#";
1163
- const style = fields?.style || "primary";
1164
- const styles = {
1165
- primary: "background-color: #2563eb; color: #ffffff; border: 2px solid #2563eb;",
1166
- secondary: "background-color: #6b7280; color: #ffffff; border: 2px solid #6b7280;",
1167
- outline: "background-color: transparent; color: #2563eb; border: 2px solid #2563eb;"
1168
- };
1169
- const buttonStyle = `${styles[style] || styles.primary} display: inline-block; padding: 12px 24px; font-size: 16px; font-weight: 600; text-decoration: none; border-radius: 6px; text-align: center;`;
1170
- return `
1171
- <div style="margin: 0 0 16px 0; text-align: center;">
1172
- <a href="${escapeHtml(url)}" target="_blank" rel="noopener noreferrer" style="${buttonStyle}">${escapeHtml(text)}</a>
1173
- </div>
1174
- `;
1175
- }
1176
- function convertDividerBlock(fields) {
1177
- const style = fields?.style || "solid";
1178
- const styles = {
1179
- solid: "border-top: 1px solid #e5e7eb;",
1180
- dashed: "border-top: 1px dashed #e5e7eb;",
1181
- dotted: "border-top: 1px dotted #e5e7eb;"
1182
- };
1183
- return `<hr style="${styles[style] || styles.solid} margin: 24px 0; border-bottom: none; border-left: none; border-right: none;" />`;
1184
- }
1185
- function getAlignment(format) {
1186
- if (!format) return "left";
1187
- if (format & 2) return "center";
1188
- if (format & 3) return "right";
1189
- if (format & 4) return "justify";
1190
- return "left";
1191
- }
1192
- function escapeHtml(text) {
1193
- const map = {
1194
- "&": "&amp;",
1195
- "<": "&lt;",
1196
- ">": "&gt;",
1197
- '"': "&quot;",
1198
- "'": "&#039;"
1199
- };
1200
- return text.replace(/[&<>"']/g, (m) => map[m]);
1201
- }
1202
- function wrapInEmailTemplate(content, preheader) {
1203
- return `<!DOCTYPE html>
1204
- <html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
1205
- <head>
1206
- <meta charset="UTF-8">
1207
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
1208
- <meta http-equiv="X-UA-Compatible" content="IE=edge">
1209
- <meta name="x-apple-disable-message-reformatting">
1210
- <title>Newsletter</title>
1211
-
1212
- <!--[if mso]>
1213
- <noscript>
1214
- <xml>
1215
- <o:OfficeDocumentSettings>
1216
- <o:PixelsPerInch>96</o:PixelsPerInch>
1217
- </o:OfficeDocumentSettings>
1218
- </xml>
1219
- </noscript>
1220
- <![endif]-->
1221
-
1222
- <style>
1223
- /* Reset and base styles */
1224
- * {
1225
- -webkit-text-size-adjust: 100%;
1226
- -ms-text-size-adjust: 100%;
1227
- }
1228
-
1229
- body {
1230
- margin: 0 !important;
1231
- padding: 0 !important;
1232
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif;
1233
- font-size: 16px;
1234
- line-height: 1.5;
1235
- color: #1A1A1A;
1236
- background-color: #f8f9fa;
1237
- -webkit-font-smoothing: antialiased;
1238
- -moz-osx-font-smoothing: grayscale;
1239
- }
1240
-
1241
- table {
1242
- border-spacing: 0 !important;
1243
- border-collapse: collapse !important;
1244
- table-layout: fixed !important;
1245
- margin: 0 auto !important;
1246
- }
1247
-
1248
- table table table {
1249
- table-layout: auto;
1250
- }
1251
-
1252
- img {
1253
- -ms-interpolation-mode: bicubic;
1254
- max-width: 100%;
1255
- height: auto;
1256
- border: 0;
1257
- outline: none;
1258
- text-decoration: none;
1259
- }
1260
-
1261
- /* Responsive styles */
1262
- @media only screen and (max-width: 640px) {
1263
- .mobile-hide {
1264
- display: none !important;
1265
- }
1266
-
1267
- .mobile-center {
1268
- text-align: center !important;
1269
- }
1270
-
1271
- .mobile-width-100 {
1272
- width: 100% !important;
1273
- max-width: 100% !important;
1274
- }
1275
-
1276
- .mobile-padding {
1277
- padding: 20px !important;
1278
- }
1279
-
1280
- .mobile-padding-sm {
1281
- padding: 16px !important;
1282
- }
1283
-
1284
- .mobile-font-size-14 {
1285
- font-size: 14px !important;
1286
- }
1287
-
1288
- .mobile-font-size-16 {
1289
- font-size: 16px !important;
1290
- }
1291
-
1292
- .mobile-font-size-20 {
1293
- font-size: 20px !important;
1294
- line-height: 1.3 !important;
1295
- }
1296
-
1297
- .mobile-font-size-24 {
1298
- font-size: 24px !important;
1299
- line-height: 1.2 !important;
1300
- }
1301
-
1302
- /* Stack sections on mobile */
1303
- .mobile-stack {
1304
- display: block !important;
1305
- width: 100% !important;
1306
- }
1307
-
1308
- /* Mobile-specific spacing */
1309
- .mobile-margin-bottom-16 {
1310
- margin-bottom: 16px !important;
1311
- }
1312
-
1313
- .mobile-margin-bottom-20 {
1314
- margin-bottom: 20px !important;
1315
- }
1316
- }
1317
-
1318
- /* Dark mode support */
1319
- @media (prefers-color-scheme: dark) {
1320
- .dark-mode-bg {
1321
- background-color: #1a1a1a !important;
1322
- }
1323
-
1324
- .dark-mode-text {
1325
- color: #ffffff !important;
1326
- }
1327
-
1328
- .dark-mode-border {
1329
- border-color: #333333 !important;
1330
- }
1331
- }
1332
-
1333
- /* Outlook-specific fixes */
1334
- <!--[if mso]>
1335
- <style>
1336
- table {
1337
- border-collapse: collapse;
1338
- border-spacing: 0;
1339
- border: none;
1340
- margin: 0;
1341
- }
1342
-
1343
- div, p {
1344
- margin: 0;
1345
- }
1346
- </style>
1347
- <![endif]-->
1348
- </style>
1349
- </head>
1350
- <body style="margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 16px; line-height: 1.5; color: #1A1A1A; background-color: #f8f9fa;">
1351
- ${preheader ? `
1352
- <!-- Preheader text -->
1353
- <div style="display: none; max-height: 0; overflow: hidden; font-size: 1px; line-height: 1px; color: transparent;">
1354
- ${escapeHtml(preheader)}
1355
- </div>
1356
- ` : ""}
1357
-
1358
- <!-- Main container -->
1359
- <table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="margin: 0; padding: 0; background-color: #f8f9fa;">
1360
- <tr>
1361
- <td align="center" style="padding: 20px 10px;">
1362
- <!-- Email wrapper -->
1363
- <table role="presentation" cellpadding="0" cellspacing="0" width="600" class="mobile-width-100" style="margin: 0 auto; max-width: 600px;">
1364
- <tr>
1365
- <td class="mobile-padding" style="padding: 0;">
1366
- <!-- Content area with light background -->
1367
- <div style="background-color: #ffffff; padding: 40px 30px; border-radius: 8px;" class="mobile-padding">
1368
- ${content}
1369
- </div>
1370
- </td>
1371
- </tr>
1372
- </table>
1373
- </td>
1374
- </tr>
1375
- </table>
1376
- </body>
1377
- </html>`;
1378
- }
1379
- function replacePersonalizationTags(html, sampleData) {
1380
- return html.replace(/\{\{([^}]+)\}\}/g, (match, tag) => {
1381
- const trimmedTag = tag.trim();
1382
- return sampleData[trimmedTag] || match;
1383
- });
1384
- }
1385
-
1386
- // src/utils/validateEmailHtml.ts
1387
- function validateEmailHtml(html) {
1388
- const warnings = [];
1389
- const errors = [];
1390
- const sizeInBytes = new Blob([html]).size;
1391
- if (sizeInBytes > 102400) {
1392
- warnings.push(`Email size (${Math.round(sizeInBytes / 1024)}KB) exceeds Gmail's 102KB limit - email may be clipped`);
1393
- }
1394
- if (html.includes("position:") && (html.includes("position: absolute") || html.includes("position: fixed"))) {
1395
- errors.push("Absolute/fixed positioning is not supported in most email clients");
1396
- }
1397
- if (html.includes("display: flex") || html.includes("display: grid")) {
1398
- errors.push("Flexbox and Grid layouts are not supported in many email clients");
1399
- }
1400
- if (html.includes("@media")) {
1401
- warnings.push("Media queries may not work in all email clients");
1402
- }
1403
- const hasJavaScript = html.includes("<script") || html.includes("onclick") || html.includes("onload") || html.includes("javascript:");
1404
- if (hasJavaScript) {
1405
- errors.push("JavaScript is not supported in email and will be stripped by email clients");
1406
- }
1407
- const hasExternalStyles = html.includes("<link") && html.includes("stylesheet");
1408
- if (hasExternalStyles) {
1409
- errors.push("External stylesheets are not supported - use inline styles only");
1410
- }
1411
- if (html.includes("<form") || html.includes("<input") || html.includes("<button")) {
1412
- errors.push("Forms and form elements are not reliably supported in email");
1413
- }
1414
- const unsupportedTags = [
1415
- "video",
1416
- "audio",
1417
- "iframe",
1418
- "embed",
1419
- "object",
1420
- "canvas",
1421
- "svg"
1422
- ];
1423
- for (const tag of unsupportedTags) {
1424
- if (html.includes(`<${tag}`)) {
1425
- errors.push(`<${tag}> tags are not supported in email`);
1426
- }
1427
- }
1428
- const imageCount = (html.match(/<img/g) || []).length;
1429
- const linkCount = (html.match(/<a/g) || []).length;
1430
- if (imageCount > 20) {
1431
- warnings.push(`High number of images (${imageCount}) may affect email performance`);
1432
- }
1433
- const imagesWithoutAlt = (html.match(/<img(?![^>]*\balt\s*=)[^>]*>/g) || []).length;
1434
- if (imagesWithoutAlt > 0) {
1435
- warnings.push(`${imagesWithoutAlt} image(s) missing alt text - important for accessibility`);
1436
- }
1437
- const linksWithoutTarget = (html.match(/<a(?![^>]*\btarget\s*=)[^>]*>/g) || []).length;
1438
- if (linksWithoutTarget > 0) {
1439
- warnings.push(`${linksWithoutTarget} link(s) missing target="_blank" attribute`);
1440
- }
1441
- if (html.includes("margin: auto") || html.includes("margin:auto")) {
1442
- warnings.push('margin: auto is not supported in Outlook - use align="center" or tables for centering');
1443
- }
1444
- if (html.includes("background-image")) {
1445
- warnings.push("Background images are not reliably supported - consider using <img> tags instead");
1446
- }
1447
- if (html.match(/\d+\s*(rem|em)/)) {
1448
- warnings.push("rem/em units may render inconsistently - use px for reliable sizing");
1449
- }
1450
- if (html.match(/margin[^:]*:\s*-\d+/)) {
1451
- errors.push("Negative margins are not supported in many email clients");
1452
- }
1453
- const personalizationTags = html.match(/\{\{([^}]+)\}\}/g) || [];
1454
- const validTags = ["subscriber.name", "subscriber.email", "subscriber.firstName", "subscriber.lastName"];
1455
- for (const tag of personalizationTags) {
1456
- const tagContent = tag.replace(/[{}]/g, "").trim();
1457
- if (!validTags.includes(tagContent)) {
1458
- warnings.push(`Unknown personalization tag: ${tag}`);
1459
- }
1460
- }
1461
- return {
1462
- valid: errors.length === 0,
1463
- warnings,
1464
- errors,
1465
- stats: {
1466
- sizeInBytes,
1467
- imageCount,
1468
- linkCount,
1469
- hasExternalStyles,
1470
- hasJavaScript
1471
- }
1472
- };
1473
- }
1474
-
1475
- // src/contexts/PluginConfigContext.tsx
1476
- var import_react5 = require("react");
1477
- var import_jsx_runtime4 = require("react/jsx-runtime");
1478
- var PluginConfigContext = (0, import_react5.createContext)(null);
1479
- var usePluginConfigOptional = () => {
1480
- return (0, import_react5.useContext)(PluginConfigContext);
1481
- };
1482
-
1483
- // src/components/Broadcasts/EmailPreview.tsx
1484
- var import_jsx_runtime5 = require("react/jsx-runtime");
1485
- var SAMPLE_DATA = {
1486
- "subscriber.name": "John Doe",
1487
- "subscriber.firstName": "John",
1488
- "subscriber.lastName": "Doe",
1489
- "subscriber.email": "john.doe@example.com"
1490
- };
1491
- var VIEWPORT_SIZES = {
1492
- desktop: { width: 600, scale: 1 },
1493
- mobile: { width: 320, scale: 0.8 }
1494
- };
1495
- var EmailPreview = ({
1496
- content,
1497
- subject,
1498
- preheader,
1499
- mode = "desktop",
1500
- onValidation,
1501
- pluginConfig: propPluginConfig
1502
- }) => {
1503
- const contextPluginConfig = usePluginConfigOptional();
1504
- const pluginConfig = propPluginConfig || contextPluginConfig;
1505
- const [html, setHtml] = (0, import_react6.useState)("");
1506
- const [loading, setLoading] = (0, import_react6.useState)(false);
1507
- const [validationResult, setValidationResult] = (0, import_react6.useState)(null);
1508
- const iframeRef = (0, import_react6.useRef)(null);
1509
- (0, import_react6.useEffect)(() => {
1510
- const convertContent = async () => {
1511
- if (!content) {
1512
- setHtml("");
1513
- return;
1514
- }
1515
- setLoading(true);
1516
- try {
1517
- const emailPreviewConfig = pluginConfig?.customizations?.broadcasts?.emailPreview;
1518
- const emailHtml = await convertToEmailSafeHtml(content, {
1519
- wrapInTemplate: emailPreviewConfig?.wrapInTemplate ?? true,
1520
- preheader,
1521
- subject,
1522
- customWrapper: emailPreviewConfig?.customWrapper,
1523
- customBlockConverter: pluginConfig?.customizations?.broadcasts?.customBlockConverter
1524
- });
1525
- const personalizedHtml = replacePersonalizationTags(emailHtml, SAMPLE_DATA);
1526
- const previewHtml = addEmailHeader(personalizedHtml, {
1527
- subject,
1528
- from: "Newsletter <noreply@example.com>",
1529
- to: SAMPLE_DATA["subscriber.email"]
1530
- });
1531
- setHtml(previewHtml);
1532
- const validation = validateEmailHtml(emailHtml);
1533
- setValidationResult(validation);
1534
- onValidation?.(validation);
1535
- } catch (error) {
1536
- console.error("Failed to convert content to HTML:", error);
1537
- setHtml("<p>Error converting content to HTML</p>");
1538
- } finally {
1539
- setLoading(false);
1540
- }
1541
- };
1542
- convertContent();
1543
- }, [content, subject, preheader, onValidation, pluginConfig]);
1544
- (0, import_react6.useEffect)(() => {
1545
- if (iframeRef.current && html) {
1546
- const doc = iframeRef.current.contentDocument;
1547
- if (doc) {
1548
- doc.open();
1549
- doc.write(html);
1550
- doc.close();
1551
- }
1552
- }
1553
- }, [html]);
1554
- const viewport = VIEWPORT_SIZES[mode];
1555
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { height: "100%", display: "flex", flexDirection: "column" }, children: [
1556
- validationResult && (validationResult.errors.length > 0 || validationResult.warnings.length > 0) && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { padding: "16px", borderBottom: "1px solid #e5e7eb" }, children: [
1557
- validationResult.errors.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { marginBottom: "12px" }, children: [
1558
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("h4", { style: { color: "#dc2626", margin: "0 0 8px 0", fontSize: "14px" }, children: [
1559
- "Errors (",
1560
- validationResult.errors.length,
1561
- ")"
1562
- ] }),
1563
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("ul", { style: { margin: 0, paddingLeft: "20px", fontSize: "13px", color: "#dc2626" }, children: validationResult.errors.map((error, index) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("li", { children: error }, index)) })
1564
- ] }),
1565
- validationResult.warnings.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { children: [
1566
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("h4", { style: { color: "#d97706", margin: "0 0 8px 0", fontSize: "14px" }, children: [
1567
- "Warnings (",
1568
- validationResult.warnings.length,
1569
- ")"
1570
- ] }),
1571
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("ul", { style: { margin: 0, paddingLeft: "20px", fontSize: "13px", color: "#d97706" }, children: validationResult.warnings.map((warning, index) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("li", { children: warning }, index)) })
1572
- ] })
1573
- ] }),
1574
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: {
1575
- flex: 1,
1576
- display: "flex",
1577
- alignItems: "center",
1578
- justifyContent: "center",
1579
- backgroundColor: "#f3f4f6",
1580
- padding: "20px",
1581
- overflow: "auto"
1582
- }, children: loading ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { textAlign: "center", color: "#6b7280" }, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { children: "Loading preview..." }) }) : html ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: {
1583
- backgroundColor: "white",
1584
- boxShadow: "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)",
1585
- borderRadius: "8px",
1586
- overflow: "hidden",
1587
- transform: `scale(${viewport.scale})`,
1588
- transformOrigin: "top center"
1589
- }, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1590
- "iframe",
1591
- {
1592
- ref: iframeRef,
1593
- title: "Email Preview",
1594
- style: {
1595
- width: `${viewport.width}px`,
1596
- height: "800px",
1597
- border: "none",
1598
- display: "block"
1599
- },
1600
- sandbox: "allow-same-origin"
1601
- }
1602
- ) }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { textAlign: "center", color: "#6b7280" }, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { children: "Start typing to see the email preview" }) }) }),
1603
- validationResult && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: {
1604
- padding: "12px 16px",
1605
- borderTop: "1px solid #e5e7eb",
1606
- fontSize: "13px",
1607
- color: "#6b7280",
1608
- display: "flex",
1609
- gap: "24px"
1610
- }, children: [
1611
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { children: [
1612
- "Size: ",
1613
- Math.round(validationResult.stats.sizeInBytes / 1024),
1614
- "KB"
1615
- ] }),
1616
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { children: [
1617
- "Links: ",
1618
- validationResult.stats.linkCount
1619
- ] }),
1620
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { children: [
1621
- "Images: ",
1622
- validationResult.stats.imageCount
1623
- ] }),
1624
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { children: [
1625
- "Viewport: ",
1626
- mode === "desktop" ? "600px" : "320px"
1627
- ] })
1628
- ] })
1629
- ] });
1630
- };
1631
- function addEmailHeader(html, headers) {
1632
- const headerHtml = `
1633
- <div style="background-color: #f9fafb; border-bottom: 1px solid #e5e7eb; padding: 16px; font-family: monospace; font-size: 13px;">
1634
- <div style="margin-bottom: 8px;"><strong>Subject:</strong> ${escapeHtml2(headers.subject)}</div>
1635
- <div style="margin-bottom: 8px;"><strong>From:</strong> ${escapeHtml2(headers.from)}</div>
1636
- <div><strong>To:</strong> ${escapeHtml2(headers.to)}</div>
1637
- </div>
1638
- `;
1639
- return html.replace(/<body[^>]*>/, `$&${headerHtml}`);
1640
- }
1641
- function escapeHtml2(text) {
1642
- const div = document.createElement("div");
1643
- div.textContent = text;
1644
- return div.innerHTML;
1645
- }
1646
-
1647
- // src/components/Broadcasts/EmailPreviewField.tsx
1648
- var import_react7 = require("react");
1649
- var import_ui = require("@payloadcms/ui");
1650
- var import_jsx_runtime6 = require("react/jsx-runtime");
1651
- var EmailPreviewField = () => {
1652
- const [previewMode, setPreviewMode] = (0, import_react7.useState)("desktop");
1653
- const [isValid, setIsValid] = (0, import_react7.useState)(true);
1654
- const [validationSummary, setValidationSummary] = (0, import_react7.useState)("");
1655
- const pluginConfig = usePluginConfigOptional();
1656
- const fields = (0, import_ui.useFormFields)(([fields2]) => ({
1657
- content: fields2["contentSection.content"],
1658
- subject: fields2["subject"],
1659
- preheader: fields2["contentSection.preheader"],
1660
- channel: fields2.channel
1661
- }));
1662
- const handleValidation = (result) => {
1663
- setIsValid(result.valid);
1664
- const errorCount = result.errors.length;
1665
- const warningCount = result.warnings.length;
1666
- if (errorCount > 0) {
1667
- setValidationSummary(`${errorCount} error${errorCount !== 1 ? "s" : ""}, ${warningCount} warning${warningCount !== 1 ? "s" : ""}`);
1668
- } else if (warningCount > 0) {
1669
- setValidationSummary(`${warningCount} warning${warningCount !== 1 ? "s" : ""}`);
1670
- } else {
1671
- setValidationSummary("");
1672
- }
1673
- };
1674
- const handleTestEmail = async () => {
1675
- const pathParts = window.location.pathname.split("/");
1676
- const broadcastId = pathParts[pathParts.length - 1];
1677
- if (!broadcastId || broadcastId === "create") {
1678
- alert("Please save the broadcast before sending a test email");
1679
- return;
1680
- }
1681
- try {
1682
- const response = await fetch(`/api/broadcasts/${broadcastId}/test`, {
1683
- method: "POST",
1684
- headers: {
1685
- "Content-Type": "application/json"
1686
- }
1687
- });
1688
- if (!response.ok) {
1689
- const data = await response.json();
1690
- throw new Error(data.error || "Failed to send test email");
1691
- }
1692
- alert("Test email sent successfully! Check your inbox.");
1693
- } catch (error) {
1694
- alert(error instanceof Error ? error.message : "Failed to send test email");
1695
- }
1696
- };
1697
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: {
1698
- marginTop: "24px",
1699
- border: "1px solid #e5e7eb",
1700
- borderRadius: "8px",
1701
- overflow: "hidden"
1702
- }, children: [
1703
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: {
1704
- display: "flex",
1705
- alignItems: "center",
1706
- justifyContent: "space-between",
1707
- padding: "12px 16px",
1708
- borderBottom: "1px solid #e5e7eb",
1709
- backgroundColor: "#f9fafb"
1710
- }, children: [
1711
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "16px" }, children: [
1712
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h3", { style: { margin: 0, fontSize: "16px", fontWeight: 600 }, children: "Email Preview" }),
1713
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", gap: "8px" }, children: [
1714
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1715
- "button",
1716
- {
1717
- type: "button",
1718
- onClick: () => setPreviewMode("desktop"),
1719
- style: {
1720
- padding: "6px 12px",
1721
- backgroundColor: previewMode === "desktop" ? "#6366f1" : "#e5e7eb",
1722
- color: previewMode === "desktop" ? "white" : "#374151",
1723
- border: "none",
1724
- borderRadius: "4px 0 0 4px",
1725
- fontSize: "14px",
1726
- cursor: "pointer"
1727
- },
1728
- children: "Desktop"
1729
- }
1730
- ),
1731
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1732
- "button",
1733
- {
1734
- type: "button",
1735
- onClick: () => setPreviewMode("mobile"),
1736
- style: {
1737
- padding: "6px 12px",
1738
- backgroundColor: previewMode === "mobile" ? "#6366f1" : "#e5e7eb",
1739
- color: previewMode === "mobile" ? "white" : "#374151",
1740
- border: "none",
1741
- borderRadius: "0 4px 4px 0",
1742
- fontSize: "14px",
1743
- cursor: "pointer"
1744
- },
1745
- children: "Mobile"
1746
- }
1747
- )
1748
- ] }),
1749
- validationSummary && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: {
1750
- padding: "6px 12px",
1751
- backgroundColor: isValid ? "#fef3c7" : "#fee2e2",
1752
- color: isValid ? "#92400e" : "#991b1b",
1753
- borderRadius: "4px",
1754
- fontSize: "13px"
1755
- }, children: validationSummary })
1756
- ] }),
1757
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1758
- "button",
1759
- {
1760
- type: "button",
1761
- onClick: handleTestEmail,
1762
- style: {
1763
- padding: "6px 12px",
1764
- backgroundColor: "#10b981",
1765
- color: "white",
1766
- border: "none",
1767
- borderRadius: "4px",
1768
- fontSize: "14px",
1769
- cursor: "pointer"
1770
- },
1771
- children: "Send Test Email"
1772
- }
1773
- )
1774
- ] }),
1775
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { height: "600px" }, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1776
- EmailPreview,
1777
- {
1778
- content: fields.content?.value || null,
1779
- subject: fields.subject?.value || "Email Subject",
1780
- preheader: fields.preheader?.value,
1781
- mode: previewMode,
1782
- onValidation: handleValidation,
1783
- pluginConfig: pluginConfig || void 0
1784
- }
1785
- ) })
1786
- ] });
1787
- };
1788
-
1789
- // src/components/Broadcasts/BroadcastEditor.tsx
1790
- var import_react8 = require("react");
1791
- var import_ui2 = require("@payloadcms/ui");
1792
- var import_jsx_runtime7 = require("react/jsx-runtime");
1793
- var BroadcastEditor = (props) => {
1794
- const { value } = (0, import_ui2.useField)({ path: props.path });
1795
- const [showPreview, setShowPreview] = (0, import_react8.useState)(true);
1796
- const [previewMode, setPreviewMode] = (0, import_react8.useState)("desktop");
1797
- const [isValid, setIsValid] = (0, import_react8.useState)(true);
1798
- const [validationSummary, setValidationSummary] = (0, import_react8.useState)("");
1799
- const fields = (0, import_ui2.useFormFields)(([fields2]) => ({
1800
- subject: fields2["subject"],
1801
- preheader: fields2["contentSection.preheader"]
1802
- }));
1803
- const handleValidation = (0, import_react8.useCallback)((result) => {
1804
- setIsValid(result.valid);
1805
- const errorCount = result.errors.length;
1806
- const warningCount = result.warnings.length;
1807
- if (errorCount > 0) {
1808
- setValidationSummary(`${errorCount} error${errorCount !== 1 ? "s" : ""}, ${warningCount} warning${warningCount !== 1 ? "s" : ""}`);
1809
- } else if (warningCount > 0) {
1810
- setValidationSummary(`${warningCount} warning${warningCount !== 1 ? "s" : ""}`);
1811
- } else {
1812
- setValidationSummary("");
1813
- }
1814
- }, []);
1815
- const handleTestEmail = async () => {
1816
- const pathParts = window.location.pathname.split("/");
1817
- const broadcastId = pathParts[pathParts.length - 1];
1818
- if (!broadcastId || broadcastId === "create") {
1819
- alert("Please save the broadcast before sending a test email");
1820
- return;
1821
- }
1822
- try {
1823
- const response = await fetch(`/api/broadcasts/${broadcastId}/test`, {
1824
- method: "POST",
1825
- headers: {
1826
- "Content-Type": "application/json"
1827
- }
1828
- });
1829
- if (!response.ok) {
1830
- const data = await response.json();
1831
- throw new Error(data.error || "Failed to send test email");
1832
- }
1833
- alert("Test email sent successfully! Check your inbox.");
1834
- } catch (error) {
1835
- alert(error instanceof Error ? error.message : "Failed to send test email");
1836
- }
1837
- };
1838
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: { height: "600px", display: "flex", flexDirection: "column" }, children: [
1839
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: {
1840
- display: "flex",
1841
- alignItems: "center",
1842
- justifyContent: "space-between",
1843
- padding: "12px 16px",
1844
- borderBottom: "1px solid #e5e7eb",
1845
- backgroundColor: "#f9fafb"
1846
- }, children: [
1847
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "16px" }, children: [
1848
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1849
- "button",
1850
- {
1851
- type: "button",
1852
- onClick: () => setShowPreview(!showPreview),
1853
- style: {
1854
- padding: "6px 12px",
1855
- backgroundColor: showPreview ? "#3b82f6" : "#e5e7eb",
1856
- color: showPreview ? "white" : "#374151",
1857
- border: "none",
1858
- borderRadius: "4px",
1859
- fontSize: "14px",
1860
- cursor: "pointer"
1861
- },
1862
- children: showPreview ? "Hide Preview" : "Show Preview"
1863
- }
1864
- ),
1865
- showPreview && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: { display: "flex", gap: "8px" }, children: [
1866
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1867
- "button",
1868
- {
1869
- type: "button",
1870
- onClick: () => setPreviewMode("desktop"),
1871
- style: {
1872
- padding: "6px 12px",
1873
- backgroundColor: previewMode === "desktop" ? "#6366f1" : "#e5e7eb",
1874
- color: previewMode === "desktop" ? "white" : "#374151",
1875
- border: "none",
1876
- borderRadius: "4px 0 0 4px",
1877
- fontSize: "14px",
1878
- cursor: "pointer"
1879
- },
1880
- children: "Desktop"
1881
- }
1882
- ),
1883
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1884
- "button",
1885
- {
1886
- type: "button",
1887
- onClick: () => setPreviewMode("mobile"),
1888
- style: {
1889
- padding: "6px 12px",
1890
- backgroundColor: previewMode === "mobile" ? "#6366f1" : "#e5e7eb",
1891
- color: previewMode === "mobile" ? "white" : "#374151",
1892
- border: "none",
1893
- borderRadius: "0 4px 4px 0",
1894
- fontSize: "14px",
1895
- cursor: "pointer"
1896
- },
1897
- children: "Mobile"
1898
- }
1899
- )
1900
- ] }),
1901
- showPreview && validationSummary && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: {
1902
- padding: "6px 12px",
1903
- backgroundColor: isValid ? "#fef3c7" : "#fee2e2",
1904
- color: isValid ? "#92400e" : "#991b1b",
1905
- borderRadius: "4px",
1906
- fontSize: "13px"
1907
- }, children: validationSummary })
1908
- ] }),
1909
- showPreview && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1910
- "button",
1911
- {
1912
- type: "button",
1913
- onClick: handleTestEmail,
1914
- style: {
1915
- padding: "6px 12px",
1916
- backgroundColor: "#10b981",
1917
- color: "white",
1918
- border: "none",
1919
- borderRadius: "4px",
1920
- fontSize: "14px",
1921
- cursor: "pointer"
1922
- },
1923
- children: "Send Test Email"
1924
- }
1925
- )
1926
- ] }),
1927
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: { flex: 1, display: "flex", overflow: "hidden" }, children: [
1928
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: {
1929
- flex: showPreview ? "0 0 50%" : "1",
1930
- overflow: "auto",
1931
- borderRight: showPreview ? "1px solid #e5e7eb" : "none"
1932
- }, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { padding: "16px" }, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "rich-text-lexical" }) }) }),
1933
- showPreview && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { flex: "0 0 50%", overflow: "hidden" }, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1934
- EmailPreview,
1935
- {
1936
- content: value,
1937
- subject: fields.subject?.value || "Email Subject",
1938
- preheader: fields.preheader?.value,
1939
- mode: previewMode,
1940
- onValidation: handleValidation
1941
- }
1942
- ) })
1943
- ] })
1944
- ] });
1945
- };
1946
-
1947
- // src/components/Broadcasts/BroadcastInlinePreview.tsx
1948
- var import_react9 = require("react");
1949
- var import_ui3 = require("@payloadcms/ui");
1950
-
1951
- // src/components/Broadcasts/PreviewControls.tsx
1952
- var import_jsx_runtime8 = require("react/jsx-runtime");
1953
- var PreviewControls = ({
1954
- onUpdate,
1955
- device,
1956
- onDeviceChange,
1957
- isLoading = false
1958
- }) => {
1959
- const controlsStyle = {
1960
- display: "flex",
1961
- alignItems: "center",
1962
- justifyContent: "space-between",
1963
- padding: "1rem",
1964
- background: "white",
1965
- borderBottom: "1px solid #e5e7eb"
1966
- };
1967
- const updateButtonStyle = {
1968
- padding: "0.5rem 1rem",
1969
- background: "#10b981",
1970
- color: "white",
1971
- border: "none",
1972
- borderRadius: "4px",
1973
- cursor: isLoading ? "not-allowed" : "pointer",
1974
- fontSize: "14px",
1975
- fontWeight: 500,
1976
- opacity: isLoading ? 0.6 : 1
1977
- };
1978
- const deviceSelectorStyle = {
1979
- display: "flex",
1980
- gap: "0.5rem"
1981
- };
1982
- const deviceButtonStyle = (isActive) => ({
1983
- display: "flex",
1984
- alignItems: "center",
1985
- gap: "0.5rem",
1986
- padding: "0.5rem 0.75rem",
1987
- background: isActive ? "#1f2937" : "white",
1988
- color: isActive ? "white" : "#374151",
1989
- border: `1px solid ${isActive ? "#1f2937" : "#e5e7eb"}`,
1990
- borderRadius: "4px",
1991
- cursor: "pointer",
1992
- fontSize: "14px"
1993
- });
1994
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: controlsStyle, children: [
1995
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1996
- "button",
1997
- {
1998
- style: updateButtonStyle,
1999
- onClick: onUpdate,
2000
- disabled: isLoading,
2001
- children: isLoading ? "Updating..." : "Update Preview"
2002
- }
2003
- ),
2004
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: deviceSelectorStyle, children: [
2005
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
2006
- "button",
2007
- {
2008
- style: deviceButtonStyle(device === "desktop"),
2009
- onClick: () => onDeviceChange("desktop"),
2010
- "aria-label": "Desktop view",
2011
- children: [
2012
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
2013
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("rect", { x: "2", y: "3", width: "20", height: "14", rx: "2", ry: "2" }),
2014
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("line", { x1: "8", y1: "21", x2: "16", y2: "21" }),
2015
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("line", { x1: "12", y1: "17", x2: "12", y2: "21" })
2016
- ] }),
2017
- "Desktop"
2018
- ]
2019
- }
2020
- ),
2021
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
2022
- "button",
2023
- {
2024
- style: deviceButtonStyle(device === "mobile"),
2025
- onClick: () => onDeviceChange("mobile"),
2026
- "aria-label": "Mobile view",
2027
- children: [
2028
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
2029
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("rect", { x: "5", y: "2", width: "14", height: "20", rx: "2", ry: "2" }),
2030
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("line", { x1: "12", y1: "18", x2: "12", y2: "18" })
2031
- ] }),
2032
- "Mobile"
2033
- ]
2034
- }
2035
- )
2036
- ] })
2037
- ] });
2038
- };
2039
-
2040
- // src/components/Broadcasts/BroadcastInlinePreview.tsx
2041
- var import_jsx_runtime9 = require("react/jsx-runtime");
2042
- var BroadcastInlinePreview = () => {
2043
- const [device, setDevice] = (0, import_react9.useState)("desktop");
2044
- const [isLoading, setIsLoading] = (0, import_react9.useState)(false);
2045
- const [showPreview, setShowPreview] = (0, import_react9.useState)(false);
2046
- const [previewHtml, setPreviewHtml] = (0, import_react9.useState)(null);
2047
- const [error, setError] = (0, import_react9.useState)(null);
2048
- const fields = (0, import_ui3.useFormFields)(([fields2]) => ({
2049
- subject: fields2["subject"]?.value,
2050
- preheader: fields2["contentSection.preheader"]?.value,
2051
- content: fields2["contentSection.content"]?.value
2052
- }));
2053
- const updatePreview = (0, import_react9.useCallback)(async () => {
2054
- if (!fields.content) {
2055
- setError(new Error("Please add some content before previewing"));
2056
- return;
2057
- }
2058
- setIsLoading(true);
2059
- setError(null);
2060
- try {
2061
- const response = await fetch("/api/broadcasts/preview", {
2062
- method: "POST",
2063
- headers: {
2064
- "Content-Type": "application/json"
2065
- },
2066
- body: JSON.stringify({
2067
- content: fields.content,
2068
- preheader: fields.preheader,
2069
- subject: fields.subject
2070
- })
2071
- });
2072
- const data = await response.json();
2073
- if (!response.ok || !data.success) {
2074
- throw new Error(data.error || "Failed to generate preview");
2075
- }
2076
- setPreviewHtml(data.preview.html);
2077
- setShowPreview(true);
2078
- } catch (err) {
2079
- setError(err);
2080
- console.error("Failed to update preview:", err);
2081
- } finally {
2082
- setIsLoading(false);
2083
- }
2084
- }, [fields]);
2085
- const containerStyle = {
2086
- border: "1px solid #e5e7eb",
2087
- borderRadius: "8px",
2088
- overflow: "hidden",
2089
- height: "100%",
2090
- display: "flex",
2091
- flexDirection: "column"
2092
- };
2093
- const headerStyle = {
2094
- display: "flex",
2095
- alignItems: "center",
2096
- justifyContent: "space-between",
2097
- padding: "1rem",
2098
- background: "#f9fafb",
2099
- borderBottom: "1px solid #e5e7eb"
2100
- };
2101
- const titleStyle = {
2102
- fontSize: "16px",
2103
- fontWeight: 600,
2104
- color: "#1f2937",
2105
- margin: 0
2106
- };
2107
- const previewContainerStyle = {
2108
- flex: 1,
2109
- display: "flex",
2110
- flexDirection: "column",
2111
- background: "#f3f4f6",
2112
- overflow: "hidden"
2113
- };
2114
- const errorStyle = {
2115
- padding: "2rem",
2116
- textAlign: "center"
2117
- };
2118
- const toggleButtonStyle = {
2119
- padding: "0.5rem 1rem",
2120
- background: showPreview ? "#ef4444" : "#3b82f6",
2121
- color: "white",
2122
- border: "none",
2123
- borderRadius: "4px",
2124
- cursor: "pointer",
2125
- fontSize: "14px",
2126
- fontWeight: 500
2127
- };
2128
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: containerStyle, children: [
2129
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: headerStyle, children: [
2130
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("h3", { style: titleStyle, children: "Email Preview" }),
2131
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2132
- "button",
2133
- {
2134
- onClick: () => showPreview ? setShowPreview(false) : updatePreview(),
2135
- style: toggleButtonStyle,
2136
- disabled: isLoading,
2137
- children: isLoading ? "Loading..." : showPreview ? "Hide Preview" : "Show Preview"
2138
- }
2139
- )
2140
- ] }),
2141
- showPreview && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: previewContainerStyle, children: error ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: errorStyle, children: [
2142
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { style: { color: "#ef4444", margin: "0 0 1rem" }, children: error.message }),
2143
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2144
- "button",
2145
- {
2146
- onClick: updatePreview,
2147
- style: {
2148
- padding: "0.5rem 1rem",
2149
- background: "#3b82f6",
2150
- color: "white",
2151
- border: "none",
2152
- borderRadius: "4px",
2153
- cursor: "pointer"
2154
- },
2155
- children: "Retry"
2156
- }
2157
- )
2158
- ] }) : previewHtml ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
2159
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2160
- PreviewControls,
2161
- {
2162
- onUpdate: updatePreview,
2163
- device,
2164
- onDeviceChange: setDevice,
2165
- isLoading
2166
- }
2167
- ),
2168
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2169
- "div",
2170
- {
2171
- style: {
2172
- flex: 1,
2173
- padding: device === "mobile" ? "1rem" : "2rem",
2174
- display: "flex",
2175
- justifyContent: "center",
2176
- overflow: "auto"
2177
- },
2178
- children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2179
- "div",
2180
- {
2181
- style: {
2182
- width: device === "mobile" ? "375px" : "600px",
2183
- maxWidth: "100%",
2184
- background: "white",
2185
- boxShadow: "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)",
2186
- borderRadius: "8px",
2187
- overflow: "hidden"
2188
- },
2189
- children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2190
- "iframe",
2191
- {
2192
- srcDoc: previewHtml,
2193
- style: {
2194
- width: "100%",
2195
- height: "100%",
2196
- minHeight: "600px",
2197
- border: "none"
2198
- },
2199
- title: "Email Preview"
2200
- }
2201
- )
2202
- }
2203
- )
2204
- }
2205
- )
2206
- ] }) : null })
2207
- ] });
2208
- };
2209
-
2210
- // src/components/Broadcasts/BroadcastPreviewField.tsx
2211
- var import_jsx_runtime10 = require("react/jsx-runtime");
2212
- var BroadcastPreviewField = () => {
2213
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { style: {
2214
- padding: "1rem",
2215
- background: "#f9fafb",
2216
- borderRadius: "4px",
2217
- fontSize: "14px",
2218
- color: "#6b7280"
2219
- }, children: "Email preview is available inline below the content editor." });
2220
- };
2221
-
2222
- // src/components/Broadcasts/EmailRenderer.tsx
2223
- var import_react10 = require("react");
2224
- var import_render = require("@react-email/render");
2225
- var import_jsx_runtime11 = require("react/jsx-runtime");
2226
- var EmailRenderer = ({
2227
- template,
2228
- data,
2229
- device = "desktop",
2230
- onRender
2231
- }) => {
2232
- const [renderedHtml, setRenderedHtml] = (0, import_react10.useState)("");
2233
- const [error, setError] = (0, import_react10.useState)(null);
2234
- const iframeRef = (0, import_react10.useRef)(null);
2235
- const renderEmail = (0, import_react10.useCallback)(async () => {
2236
- try {
2237
- const TemplateComponent = template;
2238
- const element = /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(TemplateComponent, { ...data });
2239
- const html = await (0, import_render.render)(element, {
2240
- pretty: true
2241
- });
2242
- setRenderedHtml(html);
2243
- onRender?.(html);
2244
- setError(null);
2245
- } catch (err) {
2246
- setError(err);
2247
- console.error("Failed to render email template:", err);
2248
- }
2249
- }, [template, data, onRender]);
2250
- (0, import_react10.useEffect)(() => {
2251
- renderEmail();
2252
- }, [renderEmail]);
2253
- (0, import_react10.useEffect)(() => {
2254
- if (iframeRef.current && renderedHtml) {
2255
- const iframe = iframeRef.current;
2256
- const doc = iframe.contentDocument || iframe.contentWindow?.document;
2257
- if (doc) {
2258
- doc.open();
2259
- doc.write(renderedHtml);
2260
- doc.close();
2261
- }
2262
- }
2263
- }, [renderedHtml]);
2264
- const containerStyle = {
2265
- width: "100%",
2266
- height: "100%",
2267
- display: "flex",
2268
- alignItems: "flex-start",
2269
- justifyContent: "center",
2270
- overflow: "auto",
2271
- padding: "2rem",
2272
- boxSizing: "border-box"
2273
- };
2274
- const iframeStyle = {
2275
- width: device === "mobile" ? "375px" : "600px",
2276
- height: "100%",
2277
- minHeight: "600px",
2278
- background: "white",
2279
- boxShadow: "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)",
2280
- borderRadius: device === "mobile" ? "20px" : "8px",
2281
- border: "none",
2282
- display: "block"
2283
- };
2284
- const errorStyle = {
2285
- background: "white",
2286
- border: "1px solid #ef4444",
2287
- borderRadius: "4px",
2288
- padding: "2rem",
2289
- maxWidth: "500px"
2290
- };
2291
- if (error) {
2292
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { style: errorStyle, children: [
2293
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("h3", { style: { color: "#ef4444", margin: "0 0 1rem" }, children: "Template Render Error" }),
2294
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("pre", { style: {
2295
- background: "#f9fafb",
2296
- padding: "1rem",
2297
- borderRadius: "4px",
2298
- overflowX: "auto",
2299
- fontSize: "12px",
2300
- color: "#374151",
2301
- margin: 0
2302
- }, children: error.message })
2303
- ] });
2304
- }
2305
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: containerStyle, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2306
- "iframe",
2307
- {
2308
- ref: iframeRef,
2309
- style: iframeStyle,
2310
- sandbox: "allow-same-origin",
2311
- title: "Email Preview"
2312
- }
2313
- ) });
2314
- };
2315
-
2316
- // src/components/Broadcasts/StatusBadge.tsx
2317
- var import_jsx_runtime12 = require("react/jsx-runtime");
2318
- var statusConfig = {
2319
- ["draft" /* DRAFT */]: {
2320
- label: "Draft",
2321
- color: "#6B7280",
2322
- // gray
2323
- backgroundColor: "#F3F4F6"
2324
- },
2325
- ["scheduled" /* SCHEDULED */]: {
2326
- label: "Scheduled",
2327
- color: "#2563EB",
2328
- // blue
2329
- backgroundColor: "#DBEAFE"
2330
- },
2331
- ["sending" /* SENDING */]: {
2332
- label: "Sending",
2333
- color: "#D97706",
2334
- // yellow/orange
2335
- backgroundColor: "#FEF3C7"
2336
- },
2337
- ["sent" /* SENT */]: {
2338
- label: "Sent",
2339
- color: "#059669",
2340
- // green
2341
- backgroundColor: "#D1FAE5"
2342
- },
2343
- ["failed" /* FAILED */]: {
2344
- label: "Failed",
2345
- color: "#DC2626",
2346
- // red
2347
- backgroundColor: "#FEE2E2"
2348
- },
2349
- ["paused" /* PAUSED */]: {
2350
- label: "Paused",
2351
- color: "#9333EA",
2352
- // purple
2353
- backgroundColor: "#EDE9FE"
2354
- },
2355
- ["canceled" /* CANCELED */]: {
2356
- label: "Canceled",
2357
- color: "#6B7280",
2358
- // gray
2359
- backgroundColor: "#F3F4F6"
2360
- }
2361
- };
2362
- var StatusBadge = ({ cellData }) => {
2363
- const status = cellData;
2364
- const config = statusConfig[status] || statusConfig["draft" /* DRAFT */];
2365
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2366
- "span",
2367
- {
2368
- style: {
2369
- display: "inline-flex",
2370
- alignItems: "center",
2371
- padding: "2px 10px",
2372
- borderRadius: "12px",
2373
- fontSize: "12px",
2374
- fontWeight: "500",
2375
- color: config.color,
2376
- backgroundColor: config.backgroundColor
2377
- },
2378
- children: config.label
2379
- }
2380
- );
2381
- };
2382
-
2383
- // src/components/Broadcasts/EmptyField.tsx
2384
- var EmptyField = () => {
2385
- return null;
2386
- };
2387
-
2388
- // src/email-templates/DefaultBroadcastTemplate.tsx
2389
- var import_components2 = require("@react-email/components");
2390
- var import_jsx_runtime13 = require("react/jsx-runtime");
2391
- var DefaultBroadcastTemplate = ({
2392
- subject,
2393
- preheader,
2394
- content
2395
- }) => {
2396
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_components2.Html, { children: [
2397
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_components2.Head, {}),
2398
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_components2.Preview, { children: preheader || subject }),
2399
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_components2.Body, { style: main, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_components2.Container, { style: container, children: [
2400
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_components2.Section, { style: contentSection, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { dangerouslySetInnerHTML: { __html: content } }) }),
2401
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_components2.Hr, { style: divider }),
2402
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_components2.Section, { style: footer, children: [
2403
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_components2.Text, { style: footerText, children: "You're receiving this email because you subscribed to our newsletter." }),
2404
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_components2.Text, { style: footerText, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_components2.Link, { href: "{{unsubscribe_url}}", style: footerLink, children: "Unsubscribe" }) })
2405
- ] })
2406
- ] }) })
2407
- ] });
2408
- };
2409
- var main = {
2410
- backgroundColor: "#ffffff",
2411
- fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif'
2412
- };
2413
- var container = {
2414
- margin: "0 auto",
2415
- padding: "40px 20px",
2416
- maxWidth: "600px"
2417
- };
2418
- var contentSection = {
2419
- fontSize: "16px",
2420
- lineHeight: "1.6",
2421
- color: "#374151"
2422
- };
2423
- var divider = {
2424
- borderColor: "#e5e7eb",
2425
- margin: "40px 0 20px"
2426
- };
2427
- var footer = {
2428
- textAlign: "center"
2429
- };
2430
- var footerText = {
2431
- fontSize: "14px",
2432
- lineHeight: "1.5",
2433
- color: "#6b7280",
2434
- margin: "0 0 10px"
2435
- };
2436
- var footerLink = {
2437
- color: "#6b7280",
2438
- textDecoration: "underline"
2439
- };
2440
- // Annotate the CommonJS export names for ESM import in node:
2441
- 0 && (module.exports = {
2442
- BroadcastEditor,
2443
- BroadcastInlinePreview,
2444
- BroadcastPreviewField,
2445
- DefaultBroadcastTemplate,
2446
- EmailPreview,
2447
- EmailPreviewField,
2448
- EmailRenderer,
2449
- EmptyField,
2450
- MagicLinkVerify,
2451
- NewsletterForm,
2452
- PreferencesForm,
2453
- PreviewControls,
2454
- StatusBadge,
2455
- createMagicLinkVerify,
2456
- createNewsletterForm,
2457
- createPreferencesForm,
2458
- useNewsletterAuth
2459
- });
2460
- //# sourceMappingURL=components.cjs.map