@jhits/plugin-newsletter 0.0.15 → 0.0.17
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/dist/api/email-utils.d.ts.map +1 -1
- package/dist/api/email-utils.js +45 -4
- package/dist/api/handlers/newsletters.d.ts.map +1 -1
- package/dist/api/handlers/newsletters.js +33 -16
- package/dist/api/handlers/send-newsletter.d.ts.map +1 -1
- package/dist/api/handlers/send-newsletter.js +54 -6
- package/dist/api/handlers/settings.d.ts.map +1 -1
- package/dist/api/handlers/settings.js +51 -1
- package/dist/index.d.ts +27 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -122
- package/dist/lib/blocks/BlockRenderer.d.ts.map +1 -1
- package/dist/lib/blocks/BlockRenderer.js +14 -2
- package/dist/lib/email/EmailRenderer.d.ts +1 -0
- package/dist/lib/email/EmailRenderer.d.ts.map +1 -1
- package/dist/lib/email/EmailRenderer.js +31 -19
- package/dist/lib/utils/config-resolver.d.ts +33 -0
- package/dist/lib/utils/config-resolver.d.ts.map +1 -0
- package/dist/lib/utils/config-resolver.js +47 -0
- package/dist/registry/BlockRegistry.d.ts +9 -1
- package/dist/registry/BlockRegistry.d.ts.map +1 -1
- package/dist/registry/BlockRegistry.js +126 -8
- package/dist/state/EditorContext.d.ts +11 -1
- package/dist/state/EditorContext.d.ts.map +1 -1
- package/dist/state/EditorContext.js +23 -5
- package/dist/state/types.d.ts +12 -0
- package/dist/state/types.d.ts.map +1 -1
- package/dist/types/block.d.ts +9 -0
- package/dist/types/block.d.ts.map +1 -1
- package/dist/types/newsletter.d.ts +4 -0
- package/dist/types/newsletter.d.ts.map +1 -1
- package/dist/views/CanvasEditor/BlockWrapper.d.ts.map +1 -1
- package/dist/views/CanvasEditor/BlockWrapper.js +24 -3
- package/dist/views/CanvasEditor/CanvasEditorView.d.ts.map +1 -1
- package/dist/views/CanvasEditor/CanvasEditorView.js +77 -17
- package/dist/views/CanvasEditor/EditorBody.d.ts.map +1 -1
- package/dist/views/CanvasEditor/EditorBody.js +1 -1
- package/dist/views/CanvasEditor/components/EditorCanvas.d.ts.map +1 -1
- package/dist/views/CanvasEditor/components/EditorCanvas.js +158 -100
- package/dist/views/CanvasEditor/components/EditorSidebar.d.ts +3 -1
- package/dist/views/CanvasEditor/components/EditorSidebar.d.ts.map +1 -1
- package/dist/views/CanvasEditor/components/EditorSidebar.js +3 -3
- package/dist/views/CanvasEditor/hooks/useRegisteredBlocks.d.ts +1 -1
- package/dist/views/CanvasEditor/hooks/useRegisteredBlocks.d.ts.map +1 -1
- package/dist/views/CanvasEditor/hooks/useRegisteredBlocks.js +6 -40
- package/dist/views/NewsletterManager.d.ts.map +1 -1
- package/dist/views/NewsletterManager.js +87 -5
- package/dist/views/components/DomainPromptModal.d.ts +13 -0
- package/dist/views/components/DomainPromptModal.d.ts.map +1 -0
- package/dist/views/components/DomainPromptModal.js +58 -0
- package/dist/views/components/NewsletterCard.d.ts +16 -0
- package/dist/views/components/NewsletterCard.d.ts.map +1 -0
- package/dist/views/components/NewsletterCard.js +94 -0
- package/dist/views/components/NewsletterGrid.d.ts +16 -0
- package/dist/views/components/NewsletterGrid.d.ts.map +1 -0
- package/dist/views/components/NewsletterGrid.js +13 -0
- package/dist/views/components/SendNewsletterModal.d.ts.map +1 -1
- package/dist/views/components/SendNewsletterModal.js +91 -22
- package/dist/views/components/SmtpSettingsModal.d.ts.map +1 -1
- package/dist/views/components/SmtpSettingsModal.js +10 -0
- package/dist/views/components/TestEmailModal.d.ts.map +1 -1
- package/dist/views/components/TestEmailModal.js +86 -17
- package/package.json +53 -9
- package/src/api/email-utils.ts +53 -4
- package/src/api/handlers/newsletters.ts +40 -20
- package/src/api/handlers/send-newsletter.ts +65 -6
- package/src/api/handlers/settings.ts +60 -2
- package/src/index.tsx +49 -155
- package/src/lib/blocks/BlockRenderer.tsx +16 -2
- package/src/lib/email/EmailRenderer.tsx +31 -20
- package/src/lib/utils/config-resolver.ts +71 -0
- package/src/registry/BlockRegistry.tsx +255 -0
- package/src/state/EditorContext.tsx +43 -8
- package/src/state/types.ts +16 -0
- package/src/types/block.ts +10 -0
- package/src/types/newsletter.ts +5 -0
- package/src/views/CanvasEditor/BlockWrapper.tsx +27 -2
- package/src/views/CanvasEditor/CanvasEditorView.tsx +142 -61
- package/src/views/CanvasEditor/EditorBody.tsx +17 -13
- package/src/views/CanvasEditor/components/EditorCanvas.tsx +178 -115
- package/src/views/CanvasEditor/components/EditorSidebar.tsx +57 -2
- package/src/views/CanvasEditor/hooks/useRegisteredBlocks.ts +6 -45
- package/src/views/NewsletterManager.tsx +164 -6
- package/src/views/components/DomainPromptModal.tsx +160 -0
- package/src/views/components/NewsletterCard.tsx +212 -0
- package/src/views/components/NewsletterGrid.tsx +48 -0
- package/src/views/components/SendNewsletterModal.tsx +270 -184
- package/src/views/components/SmtpSettingsModal.tsx +11 -0
- package/src/views/components/TestEmailModal.tsx +235 -149
- package/src/registry/BlockRegistry.ts +0 -53
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"email-utils.d.ts","sourceRoot":"","sources":["../../src/api/email-utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,EAAE,mBAAmB,EAAsB,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"email-utils.d.ts","sourceRoot":"","sources":["../../src/api/email-utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,EAAE,mBAAmB,EAAsB,MAAM,qBAAqB,CAAC;AA0E9E,wBAAsB,gBAAgB,CAClC,MAAM,EAAE,mBAAmB,EAC3B,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,IAAI,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC,CAgIf"}
|
package/dist/api/email-utils.js
CHANGED
|
@@ -21,10 +21,42 @@ async function getSmtpConfig(config) {
|
|
|
21
21
|
from: smtpConfig.from,
|
|
22
22
|
fromName: smtpConfig.fromName || '',
|
|
23
23
|
logoUrl: smtpConfig.logoUrl || '',
|
|
24
|
+
unsubscribeTranslations: smtpConfig.unsubscribeTranslations || {},
|
|
24
25
|
};
|
|
25
26
|
}
|
|
26
27
|
return null;
|
|
27
28
|
}
|
|
29
|
+
async function resolveBaseUrl(config, language) {
|
|
30
|
+
try {
|
|
31
|
+
const dbConnection = await config.getDb();
|
|
32
|
+
const db = dbConnection.db();
|
|
33
|
+
const settings = db.collection('settings');
|
|
34
|
+
// Try to get site config from plugin-website (stored in 'settings' collection with identifier 'site_config')
|
|
35
|
+
const siteConfig = await settings.findOne({ identifier: 'site_config' });
|
|
36
|
+
if (siteConfig && siteConfig.domainLocaleConfig && Array.isArray(siteConfig.domainLocaleConfig)) {
|
|
37
|
+
// Find domain for this locale
|
|
38
|
+
const localeConfig = siteConfig.domainLocaleConfig.find((c) => c.locale === language);
|
|
39
|
+
if (localeConfig && localeConfig.domain && localeConfig.domain !== 'undefined' && localeConfig.domain.trim() !== '') {
|
|
40
|
+
const domain = localeConfig.domain.trim();
|
|
41
|
+
// Add protocol if missing
|
|
42
|
+
if (!domain.startsWith('http')) {
|
|
43
|
+
const protocol = domain.includes('localhost') ? 'http' : 'https';
|
|
44
|
+
return `${protocol}://${domain}`;
|
|
45
|
+
}
|
|
46
|
+
return domain;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
console.warn('[NewsletterAPI] Failed to resolve language-specific base URL:', error);
|
|
52
|
+
}
|
|
53
|
+
// Fallback to default, carefully checking for 'undefined' string
|
|
54
|
+
const fallback = process.env.NEXT_PUBLIC_SITE_URL;
|
|
55
|
+
if (fallback && fallback !== 'undefined' && fallback.trim() !== '') {
|
|
56
|
+
return fallback;
|
|
57
|
+
}
|
|
58
|
+
return 'https://botanicsandyou.com';
|
|
59
|
+
}
|
|
28
60
|
export async function sendWelcomeEmail(config, email, language, host) {
|
|
29
61
|
const smtpConfig = await getSmtpConfig(config);
|
|
30
62
|
if (!smtpConfig)
|
|
@@ -32,9 +64,8 @@ export async function sendWelcomeEmail(config, email, language, host) {
|
|
|
32
64
|
const { blocks, metadata } = await GET_WELCOME_EMAIL_CONTENT(undefined, config, language);
|
|
33
65
|
const isDutch = language === 'nl';
|
|
34
66
|
const isSwedish = language === 'sv';
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
: config.baseUrl || 'https://bya.jorishummel.com';
|
|
67
|
+
// Resolve base URL based on language settings from plugin-website
|
|
68
|
+
const baseUrl = await resolveBaseUrl(config, language);
|
|
38
69
|
const slugs = {
|
|
39
70
|
sv: '/avmälla',
|
|
40
71
|
nl: '/afmelden',
|
|
@@ -75,11 +106,17 @@ export async function sendWelcomeEmail(config, email, language, host) {
|
|
|
75
106
|
}
|
|
76
107
|
if (blocks && blocks.length > 0) {
|
|
77
108
|
const { generateNewsletterEmailHtml } = await import('../lib/email/EmailRenderer');
|
|
109
|
+
// Get unsubscribe text (Priority: metadata manual override > global SMTP translation > hardcoded localized default)
|
|
110
|
+
const globalTranslations = smtpConfig.unsubscribeTranslations || {};
|
|
111
|
+
const hardcodedDefaults = { en: 'Unsubscribe', nl: 'Afmelden', sv: 'Avanmälan' };
|
|
112
|
+
const defaultText = globalTranslations[language] || hardcodedDefaults[language] || hardcodedDefaults.en;
|
|
113
|
+
const unsubscribeText = metadata?.unsubscribeText || defaultText;
|
|
78
114
|
html = generateNewsletterEmailHtml(blocks, { subject: metadata?.subject || '', previewText: metadata?.previewText || '' }, {
|
|
79
115
|
baseUrl,
|
|
80
116
|
locale: language,
|
|
81
117
|
logoUrl: logoSrc,
|
|
82
118
|
unsubscribeUrl,
|
|
119
|
+
unsubscribeText,
|
|
83
120
|
footerText: `© ${new Date().getFullYear()} ${smtpConfig.fromName || 'Botanics & You'}`,
|
|
84
121
|
});
|
|
85
122
|
subject = metadata?.subject || (isDutch ? 'Welkom!' : isSwedish ? 'Välkommen!' : 'Welcome!');
|
|
@@ -100,6 +137,10 @@ export async function sendWelcomeEmail(config, email, language, host) {
|
|
|
100
137
|
return;
|
|
101
138
|
}
|
|
102
139
|
}
|
|
140
|
+
// Get unsubscribe text for legacy template too
|
|
141
|
+
const globalTranslations = smtpConfig.unsubscribeTranslations || {};
|
|
142
|
+
const hardcodedDefaults = { en: 'Unsubscribe', nl: 'Afmelden', sv: 'Avanmälan' };
|
|
143
|
+
const unsubscribeText = globalTranslations[language] || hardcodedDefaults[language] || hardcodedDefaults.en;
|
|
103
144
|
const template = handlebars.compile(templateContent);
|
|
104
145
|
html = template({
|
|
105
146
|
fromName: smtpConfig.fromName || 'Botanics & You',
|
|
@@ -110,7 +151,7 @@ export async function sendWelcomeEmail(config, email, language, host) {
|
|
|
110
151
|
? 'Tack för att du är en del av vår community. Vi tror att naturen ger oss allt vi verkligen behöver.'
|
|
111
152
|
: 'Thank you for joining our community. We believe that nature provides everything we truly need.',
|
|
112
153
|
unsubscribeUrl,
|
|
113
|
-
unsubscribeText
|
|
154
|
+
unsubscribeText,
|
|
114
155
|
tagline: isDutch ? 'Natuurlijk verbonden' : isSwedish ? 'Naturligt ansluten' : 'Naturally connected',
|
|
115
156
|
currentYear: new Date().getFullYear(),
|
|
116
157
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"newsletters.d.ts","sourceRoot":"","sources":["../../../src/api/handlers/newsletters.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,mBAAmB,EAAkC,MAAM,wBAAwB,CAAC;AAc7F,wBAAsB,eAAe,CACjC,GAAG,EAAE,WAAW,EAChB,MAAM,EAAE,mBAAmB,GAC5B,OAAO,CAAC,YAAY,CAAC,
|
|
1
|
+
{"version":3,"file":"newsletters.d.ts","sourceRoot":"","sources":["../../../src/api/handlers/newsletters.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,mBAAmB,EAAkC,MAAM,wBAAwB,CAAC;AAc7F,wBAAsB,eAAe,CACjC,GAAG,EAAE,WAAW,EAChB,MAAM,EAAE,mBAAmB,GAC5B,OAAO,CAAC,YAAY,CAAC,CAsFvB;AAED,wBAAsB,cAAc,CAChC,GAAG,EAAE,WAAW,EAChB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,mBAAmB,GAC5B,OAAO,CAAC,YAAY,CAAC,CAuEvB;AAED,wBAAsB,eAAe,CACjC,GAAG,EAAE,WAAW,EAChB,MAAM,EAAE,mBAAmB,GAC5B,OAAO,CAAC,YAAY,CAAC,CAqEvB;AAED,wBAAsB,cAAc,CAChC,GAAG,EAAE,WAAW,EAChB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,mBAAmB,GAC5B,OAAO,CAAC,YAAY,CAAC,CA+FvB;AAED,wBAAsB,iBAAiB,CACnC,GAAG,EAAE,WAAW,EAChB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,mBAAmB,GAC5B,OAAO,CAAC,YAAY,CAAC,CA8BvB"}
|
|
@@ -29,6 +29,7 @@ export async function GET_NEWSLETTERS(req, config) {
|
|
|
29
29
|
const skip = parseInt(searchParams.get('skip') || '0', 10);
|
|
30
30
|
const sortBy = searchParams.get('sortBy') || 'updatedAt';
|
|
31
31
|
const sortOrder = searchParams.get('sortOrder') || 'desc';
|
|
32
|
+
const language = searchParams.get('language') || 'en';
|
|
32
33
|
const query = {};
|
|
33
34
|
if (status) {
|
|
34
35
|
query['publication.status'] = status;
|
|
@@ -49,20 +50,36 @@ export async function GET_NEWSLETTERS(req, config) {
|
|
|
49
50
|
.limit(limit)
|
|
50
51
|
.skip(skip)
|
|
51
52
|
.toArray();
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
53
|
+
const primaryLanguage = 'en'; // Default primary
|
|
54
|
+
const listItems = newsletterList.map((newsletter) => {
|
|
55
|
+
const languages = newsletter.languages || {};
|
|
56
|
+
const newsletterPrimaryLang = newsletter.metadata?.lang || primaryLanguage;
|
|
57
|
+
// Get title (from subject) and subject from language-specific content
|
|
58
|
+
let title = newsletter.metadata?.subject || '';
|
|
59
|
+
if (languages[language]) {
|
|
60
|
+
title = languages[language].metadata?.subject || title;
|
|
61
|
+
}
|
|
62
|
+
else if (language !== newsletterPrimaryLang && languages[newsletterPrimaryLang]) {
|
|
63
|
+
// Fall back to primary language
|
|
64
|
+
title = languages[newsletterPrimaryLang].metadata?.subject || title;
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
id: newsletter._id?.toString() || newsletter.id,
|
|
68
|
+
title: title || newsletter.title || 'Untitled',
|
|
69
|
+
slug: newsletter.slug,
|
|
70
|
+
status: newsletter.publication?.status || 'draft',
|
|
71
|
+
subject: title,
|
|
72
|
+
scheduledDate: newsletter.publication?.scheduledDate,
|
|
73
|
+
sentDate: newsletter.publication?.sentDate,
|
|
74
|
+
authorId: newsletter.publication?.authorId,
|
|
75
|
+
updatedAt: newsletter.updatedAt || newsletter.createdAt,
|
|
76
|
+
recipientCount: newsletter.recipientCount,
|
|
77
|
+
hidden: newsletter.hidden,
|
|
78
|
+
sendHistory: newsletter.sendHistory || [],
|
|
79
|
+
availableLanguages: Object.keys(languages),
|
|
80
|
+
languages,
|
|
81
|
+
};
|
|
82
|
+
});
|
|
66
83
|
return NextResponse.json(listItems);
|
|
67
84
|
}
|
|
68
85
|
catch (error) {
|
|
@@ -108,7 +125,7 @@ export async function GET_NEWSLETTER(req, idOrSlug, config) {
|
|
|
108
125
|
}
|
|
109
126
|
const result = {
|
|
110
127
|
id: newsletter._id?.toString() || newsletter.id,
|
|
111
|
-
title:
|
|
128
|
+
title: metadata.subject || 'Untitled',
|
|
112
129
|
slug: newsletter.slug,
|
|
113
130
|
blocks,
|
|
114
131
|
metadata,
|
|
@@ -236,7 +253,7 @@ export async function PUT_NEWSLETTER(req, idOrSlug, config) {
|
|
|
236
253
|
// Set primary language if not set
|
|
237
254
|
const primaryLanguage = existing.metadata?.lang || language;
|
|
238
255
|
const updateData = {
|
|
239
|
-
title: finalTitle,
|
|
256
|
+
title: finalTitle, // Keep root title for backwards compatibility
|
|
240
257
|
blocks: blocks || [], // Keep blocks at root for backwards compatibility
|
|
241
258
|
metadata: {
|
|
242
259
|
subject: metadata.subject.trim(),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"send-newsletter.d.ts","sourceRoot":"","sources":["../../../src/api/handlers/send-newsletter.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"send-newsletter.d.ts","sourceRoot":"","sources":["../../../src/api/handlers/send-newsletter.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAiF7D,wBAAsB,oBAAoB,CACtC,GAAG,EAAE,WAAW,EAChB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,mBAAmB,GAC5B,OAAO,CAAC,YAAY,CAAC,CA8OvB;AAED,wBAAsB,uBAAuB,CACzC,GAAG,EAAE,WAAW,EAChB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,mBAAmB,GAC5B,OAAO,CAAC,YAAY,CAAC,CAuCvB"}
|
|
@@ -19,6 +19,7 @@ async function getSmtpConfig(config) {
|
|
|
19
19
|
from: smtpConfig.from,
|
|
20
20
|
fromName: smtpConfig.fromName || '',
|
|
21
21
|
logoUrl: smtpConfig.logoUrl || '',
|
|
22
|
+
unsubscribeTranslations: smtpConfig.unsubscribeTranslations || {},
|
|
22
23
|
};
|
|
23
24
|
}
|
|
24
25
|
return null;
|
|
@@ -29,6 +30,37 @@ function getNewsletterFilter(idOrSlug) {
|
|
|
29
30
|
}
|
|
30
31
|
return { slug: idOrSlug };
|
|
31
32
|
}
|
|
33
|
+
async function resolveBaseUrl(config, language) {
|
|
34
|
+
try {
|
|
35
|
+
const dbConnection = await config.getDb();
|
|
36
|
+
const db = dbConnection.db();
|
|
37
|
+
const settings = db.collection('settings');
|
|
38
|
+
// Try to get site config from plugin-website (stored in 'settings' collection with identifier 'site_config')
|
|
39
|
+
const siteConfig = await settings.findOne({ identifier: 'site_config' });
|
|
40
|
+
if (siteConfig && siteConfig.domainLocaleConfig && Array.isArray(siteConfig.domainLocaleConfig)) {
|
|
41
|
+
// Find domain for this locale
|
|
42
|
+
const localeConfig = siteConfig.domainLocaleConfig.find((c) => c.locale === language);
|
|
43
|
+
if (localeConfig && localeConfig.domain && localeConfig.domain !== 'undefined' && localeConfig.domain.trim() !== '') {
|
|
44
|
+
const domain = localeConfig.domain.trim();
|
|
45
|
+
// Add protocol if missing
|
|
46
|
+
if (!domain.startsWith('http')) {
|
|
47
|
+
const protocol = domain.includes('localhost') ? 'http' : 'https';
|
|
48
|
+
return `${protocol}://${domain}`;
|
|
49
|
+
}
|
|
50
|
+
return domain;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
console.warn('[NewsletterAPI] Failed to resolve language-specific base URL:', error);
|
|
56
|
+
}
|
|
57
|
+
// Fallback to default, carefully checking for 'undefined' string
|
|
58
|
+
const fallback = process.env.NEXT_PUBLIC_SITE_URL;
|
|
59
|
+
if (fallback && fallback !== 'undefined' && fallback.trim() !== '') {
|
|
60
|
+
return fallback;
|
|
61
|
+
}
|
|
62
|
+
return 'https://botanicsandyou.com';
|
|
63
|
+
}
|
|
32
64
|
export async function POST_SEND_NEWSLETTER(req, idOrSlug, config) {
|
|
33
65
|
try {
|
|
34
66
|
const userId = await config.getUserId?.(req);
|
|
@@ -79,7 +111,15 @@ export async function POST_SEND_NEWSLETTER(req, idOrSlug, config) {
|
|
|
79
111
|
},
|
|
80
112
|
connectionTimeout: 30000,
|
|
81
113
|
});
|
|
82
|
-
|
|
114
|
+
// Resolve base URL based on language settings from plugin-website
|
|
115
|
+
const baseUrl = await resolveBaseUrl(config, language);
|
|
116
|
+
// Final sanity check - if domain is STILL undefined, stop sending and ask user for domain
|
|
117
|
+
if (baseUrl.includes('undefined')) {
|
|
118
|
+
return NextResponse.json({
|
|
119
|
+
error: 'Domain not configured for this language. Please define your website domain first.',
|
|
120
|
+
code: 'DOMAIN_MISSING'
|
|
121
|
+
}, { status: 400 });
|
|
122
|
+
}
|
|
83
123
|
let logoAttachment = undefined;
|
|
84
124
|
let logoSrc = smtpConfig.logoUrl || `${baseUrl}/logo_black.svg`;
|
|
85
125
|
if (smtpConfig.logoUrl && smtpConfig.logoUrl.startsWith('data:')) {
|
|
@@ -132,18 +172,26 @@ export async function POST_SEND_NEWSLETTER(req, idOrSlug, config) {
|
|
|
132
172
|
const subscriber = isTest ? null : await subscribers.findOne({ email });
|
|
133
173
|
const subscriberLang = subscriber?.language || language;
|
|
134
174
|
const subscriberSlug = slugs[subscriberLang] || slugs.en;
|
|
135
|
-
|
|
136
|
-
|
|
175
|
+
// Resolve correct base URL for this specific subscriber's language
|
|
176
|
+
const subscriberBaseUrl = await resolveBaseUrl(config, subscriberLang);
|
|
177
|
+
const unsubscribeUrl = `${subscriberBaseUrl}${subscriberSlug}?email=${encodeURIComponent(email)}`;
|
|
178
|
+
return { email, unsubscribeUrl, subscriberLang, subscriberBaseUrl };
|
|
137
179
|
}));
|
|
138
180
|
let successCount = 0;
|
|
139
181
|
let failedCount = 0;
|
|
140
|
-
for (const { email, unsubscribeUrl } of recipientsWithUrls) {
|
|
182
|
+
for (const { email, unsubscribeUrl, subscriberLang, subscriberBaseUrl } of recipientsWithUrls) {
|
|
141
183
|
try {
|
|
184
|
+
// Get unsubscribe text (Priority: metadata manual override > global SMTP translation > hardcoded localized default)
|
|
185
|
+
const globalTranslations = smtpConfig.unsubscribeTranslations || {};
|
|
186
|
+
const hardcodedDefaults = { en: 'Unsubscribe', nl: 'Afmelden', sv: 'Avanmälan' };
|
|
187
|
+
const defaultText = globalTranslations[subscriberLang] || hardcodedDefaults[subscriberLang] || hardcodedDefaults.en;
|
|
188
|
+
const unsubscribeText = metadata?.unsubscribeText || defaultText;
|
|
142
189
|
const html = generateNewsletterEmailHtml(blocks, { subject: metadata.subject || '', previewText: metadata.previewText || '' }, {
|
|
143
|
-
baseUrl,
|
|
144
|
-
locale:
|
|
190
|
+
baseUrl: subscriberBaseUrl,
|
|
191
|
+
locale: subscriberLang,
|
|
145
192
|
logoUrl: logoSrc,
|
|
146
193
|
unsubscribeUrl,
|
|
194
|
+
unsubscribeText,
|
|
147
195
|
footerText: `© ${new Date().getFullYear()} ${smtpConfig.fromName || 'Botanics & You'}`,
|
|
148
196
|
});
|
|
149
197
|
await transporter.sendMail({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../../../src/api/handlers/settings.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,mBAAmB,EAAsB,MAAM,wBAAwB,CAAC;AAoBjF,wBAAsB,YAAY,CAC9B,GAAG,EAAE,WAAW,EAChB,MAAM,EAAE,mBAAmB,GAC5B,OAAO,CAAC,YAAY,CAAC,CA2BvB;AAED,wBAAsB,aAAa,CAC/B,GAAG,EAAE,WAAW,EAChB,MAAM,EAAE,mBAAmB,GAC5B,OAAO,CAAC,YAAY,CAAC,CAgCvB;AAED,wBAAsB,iBAAiB,CACnC,GAAG,EAAE,WAAW,EAChB,MAAM,EAAE,mBAAmB,GAC5B,OAAO,CAAC,YAAY,CAAC,
|
|
1
|
+
{"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../../../src/api/handlers/settings.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,mBAAmB,EAAsB,MAAM,wBAAwB,CAAC;AAoBjF,wBAAsB,YAAY,CAC9B,GAAG,EAAE,WAAW,EAChB,MAAM,EAAE,mBAAmB,GAC5B,OAAO,CAAC,YAAY,CAAC,CA2BvB;AAED,wBAAsB,aAAa,CAC/B,GAAG,EAAE,WAAW,EAChB,MAAM,EAAE,mBAAmB,GAC5B,OAAO,CAAC,YAAY,CAAC,CAgCvB;AAED,wBAAsB,iBAAiB,CACnC,GAAG,EAAE,WAAW,EAChB,MAAM,EAAE,mBAAmB,GAC5B,OAAO,CAAC,YAAY,CAAC,CAqDvB;AAED,wBAAsB,kBAAkB,CACpC,GAAG,EAAE,WAAW,EAChB,MAAM,EAAE,mBAAmB,GAC5B,OAAO,CAAC,YAAY,CAAC,CAiDvB;AA2DD,wBAAsB,eAAe,CACjC,GAAG,EAAE,WAAW,EAChB,MAAM,EAAE,mBAAmB,GAC5B,OAAO,CAAC,YAAY,CAAC,CAgMvB"}
|
|
@@ -86,6 +86,11 @@ export async function GET_SMTP_SETTINGS(req, config) {
|
|
|
86
86
|
fromName: '',
|
|
87
87
|
primaryLanguage: 'en',
|
|
88
88
|
logoUrl: '',
|
|
89
|
+
unsubscribeTranslations: {
|
|
90
|
+
en: 'Unsubscribe',
|
|
91
|
+
nl: 'Afmelden',
|
|
92
|
+
sv: 'Avanmälan',
|
|
93
|
+
},
|
|
89
94
|
});
|
|
90
95
|
}
|
|
91
96
|
return NextResponse.json({
|
|
@@ -97,6 +102,11 @@ export async function GET_SMTP_SETTINGS(req, config) {
|
|
|
97
102
|
fromName: smtpConfig.fromName || '',
|
|
98
103
|
primaryLanguage: smtpConfig.primaryLanguage || 'en',
|
|
99
104
|
logoUrl: smtpConfig.logoUrl || '',
|
|
105
|
+
unsubscribeTranslations: smtpConfig.unsubscribeTranslations || {
|
|
106
|
+
en: 'Unsubscribe',
|
|
107
|
+
nl: 'Afmelden',
|
|
108
|
+
sv: 'Avanmälan',
|
|
109
|
+
},
|
|
100
110
|
});
|
|
101
111
|
}
|
|
102
112
|
catch (error) {
|
|
@@ -128,6 +138,7 @@ export async function POST_SMTP_SETTINGS(req, config) {
|
|
|
128
138
|
fromName: body.fromName || '',
|
|
129
139
|
primaryLanguage: body.primaryLanguage || 'en',
|
|
130
140
|
logoUrl: body.logoUrl || '',
|
|
141
|
+
unsubscribeTranslations: body.unsubscribeTranslations || {},
|
|
131
142
|
updatedAt: new Date(),
|
|
132
143
|
updatedBy: userId,
|
|
133
144
|
},
|
|
@@ -159,6 +170,37 @@ function getSmtpConfigFromDb(config) {
|
|
|
159
170
|
return null;
|
|
160
171
|
})();
|
|
161
172
|
}
|
|
173
|
+
async function resolveBaseUrl(config, language) {
|
|
174
|
+
try {
|
|
175
|
+
const dbConnection = await config.getDb();
|
|
176
|
+
const db = dbConnection.db();
|
|
177
|
+
const settings = db.collection('settings');
|
|
178
|
+
// Try to get site config from plugin-website (stored in 'settings' collection with identifier 'site_config')
|
|
179
|
+
const siteConfig = await settings.findOne({ identifier: 'site_config' });
|
|
180
|
+
if (siteConfig && siteConfig.domainLocaleConfig && Array.isArray(siteConfig.domainLocaleConfig)) {
|
|
181
|
+
// Find domain for this locale
|
|
182
|
+
const localeConfig = siteConfig.domainLocaleConfig.find((c) => c.locale === language);
|
|
183
|
+
if (localeConfig && localeConfig.domain && localeConfig.domain !== 'undefined' && localeConfig.domain.trim() !== '') {
|
|
184
|
+
const domain = localeConfig.domain.trim();
|
|
185
|
+
// Add protocol if missing
|
|
186
|
+
if (!domain.startsWith('http')) {
|
|
187
|
+
const protocol = domain.includes('localhost') ? 'http' : 'https';
|
|
188
|
+
return `${protocol}://${domain}`;
|
|
189
|
+
}
|
|
190
|
+
return domain;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
catch (error) {
|
|
195
|
+
console.warn('[NewsletterAPI] Failed to resolve language-specific base URL:', error);
|
|
196
|
+
}
|
|
197
|
+
// Fallback to default, carefully checking for 'undefined' string
|
|
198
|
+
const fallback = process.env.NEXT_PUBLIC_SITE_URL;
|
|
199
|
+
if (fallback && fallback !== 'undefined' && fallback.trim() !== '') {
|
|
200
|
+
return fallback;
|
|
201
|
+
}
|
|
202
|
+
return 'https://botanicsandyou.com';
|
|
203
|
+
}
|
|
162
204
|
export async function POST_TEST_EMAIL(req, config) {
|
|
163
205
|
try {
|
|
164
206
|
const userId = await config.getUserId?.(req);
|
|
@@ -194,7 +236,15 @@ export async function POST_TEST_EMAIL(req, config) {
|
|
|
194
236
|
const altPath = path.join(__dirname, '..', '..', '..', '..', 'templates', 'logo.png');
|
|
195
237
|
logoExists = fs.existsSync(altPath);
|
|
196
238
|
}
|
|
197
|
-
|
|
239
|
+
// Resolve base URL based on language settings from plugin-website
|
|
240
|
+
const baseUrl = await resolveBaseUrl(config, language);
|
|
241
|
+
// Final sanity check - if domain is STILL undefined, stop sending and ask user for domain
|
|
242
|
+
if (baseUrl.includes('undefined')) {
|
|
243
|
+
return NextResponse.json({
|
|
244
|
+
error: 'Domain not configured for this language. Please define your website domain first.',
|
|
245
|
+
code: 'DOMAIN_MISSING'
|
|
246
|
+
}, { status: 400 });
|
|
247
|
+
}
|
|
198
248
|
let logoAttachment = undefined;
|
|
199
249
|
let logoSrc = smtpConfig.logoUrl || `${baseUrl}/logo_black.svg`;
|
|
200
250
|
if (smtpConfig.logoUrl && smtpConfig.logoUrl.startsWith('data:')) {
|
package/dist/index.d.ts
CHANGED
|
@@ -24,23 +24,40 @@ export interface PluginProps {
|
|
|
24
24
|
/** Background color for dark mode (optional) */
|
|
25
25
|
dark?: string;
|
|
26
26
|
};
|
|
27
|
+
/** Localized strings for modular blocks in the editor */
|
|
28
|
+
translations?: Record<string, any>;
|
|
29
|
+
/** Global translations for unsubscribe text */
|
|
30
|
+
unsubscribeTranslations?: Record<string, string>;
|
|
27
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* Client-facing configuration type
|
|
34
|
+
* Allows partial overrides of plugin behavior
|
|
35
|
+
*/
|
|
36
|
+
export type NewsletterPluginConfig = Partial<Omit<PluginProps, 'subPath' | 'siteId' | 'locale'>> & {
|
|
37
|
+
customBlocks?: ClientBlockDefinition[];
|
|
38
|
+
darkMode?: boolean;
|
|
39
|
+
backgroundColors?: {
|
|
40
|
+
light: string;
|
|
41
|
+
dark?: string;
|
|
42
|
+
};
|
|
43
|
+
translations?: Record<string, any>;
|
|
44
|
+
unsubscribeTranslations?: Record<string, string>;
|
|
45
|
+
emailConfig?: {
|
|
46
|
+
logoUrl?: string;
|
|
47
|
+
logoAlt?: string;
|
|
48
|
+
footerText?: string;
|
|
49
|
+
primaryColor?: string;
|
|
50
|
+
};
|
|
51
|
+
};
|
|
28
52
|
/**
|
|
29
53
|
* Main Router Component
|
|
30
54
|
* Handles routing within the newsletter plugin
|
|
31
|
-
*
|
|
32
|
-
* Client Handshake:
|
|
33
|
-
* - Client apps can pass customBlocks via props
|
|
34
|
-
* - Or via window.__JHITS_PLUGIN_PROPS__['plugin-newsletter'].customBlocks
|
|
35
|
-
* - The EditorProvider will automatically register these blocks
|
|
36
55
|
*/
|
|
37
56
|
export default function NewsletterPlugin(props: PluginProps): import("react/jsx-runtime").JSX.Element;
|
|
38
57
|
export { NewsletterPlugin as Index };
|
|
39
|
-
export type { Block,
|
|
40
|
-
export type { Newsletter, NewsletterStatus, NewsletterMetadata, NewsletterListItem, NewsletterFilterOptions, } from './types/newsletter';
|
|
58
|
+
export type { Block, ClientBlockDefinition } from './types/block';
|
|
41
59
|
export { initNewsletterPlugin } from './init';
|
|
42
|
-
export type { NewsletterPluginConfig } from './init';
|
|
43
|
-
export { EditorProvider, useEditor } from './state/EditorContext';
|
|
44
|
-
export type { EditorProviderProps, EditorState, EditorContextValue } from './state';
|
|
45
60
|
export { blockRegistry } from './registry';
|
|
61
|
+
export { EditorProvider, useEditor } from './state/EditorContext';
|
|
62
|
+
export { BlockRenderer, BlocksRenderer } from './lib/blocks/BlockRenderer';
|
|
46
63
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAQtD;;;GAGG;AACH,MAAM,WAAW,WAAW;IACxB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,yGAAyG;IACzG,YAAY,CAAC,EAAE,qBAAqB,EAAE,CAAC;IACvC,qEAAqE;IACrE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uCAAuC;IACvC,gBAAgB,CAAC,EAAE;QACf,iDAAiD;QACjD,KAAK,EAAE,MAAM,CAAC;QACd,gDAAgD;QAChD,IAAI,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,yDAAyD;IACzD,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACnC,+CAA+C;IAC/C,uBAAuB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpD;AAED;;;GAGG;AACH,MAAM,MAAM,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,SAAS,GAAG,QAAQ,GAAG,QAAQ,CAAC,CAAC,GAAG;IAC/F,YAAY,CAAC,EAAE,qBAAqB,EAAE,CAAC;IACvC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,gBAAgB,CAAC,EAAE;QACf,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACnC,uBAAuB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjD,WAAW,CAAC,EAAE;QACV,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,YAAY,CAAC,EAAE,MAAM,CAAC;KACzB,CAAC;CACL,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,OAAO,UAAU,gBAAgB,CAAC,KAAK,EAAE,WAAW,2CA4K1D;AAGD,OAAO,EAAE,gBAAgB,IAAI,KAAK,EAAE,CAAC;AACrC,YAAY,EAAE,KAAK,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAClE,OAAO,EAAE,oBAAoB,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClE,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC"}
|