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.
- package/CHANGELOG.md +55 -0
- package/dist/admin.js +104 -0
- package/dist/broadcast-VMCYSZRY.js +6 -0
- package/dist/chunk-XVMYJQRQ.js +490 -0
- package/dist/client.d.ts +131 -15
- package/dist/client.js +1 -1
- package/dist/server.d.ts +735 -0
- package/dist/{index.js → server.js} +30 -654
- package/package.json +19 -28
- package/dist/client.cjs +0 -891
- package/dist/client.cjs.map +0 -1
- package/dist/client.d.cts +0 -53
- package/dist/client.js.map +0 -1
- package/dist/components.cjs +0 -2460
- package/dist/components.cjs.map +0 -1
- package/dist/components.d.cts +0 -66
- package/dist/components.d.ts +0 -66
- package/dist/components.js +0 -2418
- package/dist/components.js.map +0 -1
- package/dist/index.cjs +0 -5545
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -90
- package/dist/index.d.ts +0 -90
- package/dist/index.js.map +0 -1
package/dist/components.cjs
DELETED
|
@@ -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;"> </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
|
-
"&": "&",
|
|
1195
|
-
"<": "<",
|
|
1196
|
-
">": ">",
|
|
1197
|
-
'"': """,
|
|
1198
|
-
"'": "'"
|
|
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
|