@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.
Files changed (90) hide show
  1. package/dist/api/email-utils.d.ts.map +1 -1
  2. package/dist/api/email-utils.js +45 -4
  3. package/dist/api/handlers/newsletters.d.ts.map +1 -1
  4. package/dist/api/handlers/newsletters.js +33 -16
  5. package/dist/api/handlers/send-newsletter.d.ts.map +1 -1
  6. package/dist/api/handlers/send-newsletter.js +54 -6
  7. package/dist/api/handlers/settings.d.ts.map +1 -1
  8. package/dist/api/handlers/settings.js +51 -1
  9. package/dist/index.d.ts +27 -10
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +15 -122
  12. package/dist/lib/blocks/BlockRenderer.d.ts.map +1 -1
  13. package/dist/lib/blocks/BlockRenderer.js +14 -2
  14. package/dist/lib/email/EmailRenderer.d.ts +1 -0
  15. package/dist/lib/email/EmailRenderer.d.ts.map +1 -1
  16. package/dist/lib/email/EmailRenderer.js +31 -19
  17. package/dist/lib/utils/config-resolver.d.ts +33 -0
  18. package/dist/lib/utils/config-resolver.d.ts.map +1 -0
  19. package/dist/lib/utils/config-resolver.js +47 -0
  20. package/dist/registry/BlockRegistry.d.ts +9 -1
  21. package/dist/registry/BlockRegistry.d.ts.map +1 -1
  22. package/dist/registry/BlockRegistry.js +126 -8
  23. package/dist/state/EditorContext.d.ts +11 -1
  24. package/dist/state/EditorContext.d.ts.map +1 -1
  25. package/dist/state/EditorContext.js +23 -5
  26. package/dist/state/types.d.ts +12 -0
  27. package/dist/state/types.d.ts.map +1 -1
  28. package/dist/types/block.d.ts +9 -0
  29. package/dist/types/block.d.ts.map +1 -1
  30. package/dist/types/newsletter.d.ts +4 -0
  31. package/dist/types/newsletter.d.ts.map +1 -1
  32. package/dist/views/CanvasEditor/BlockWrapper.d.ts.map +1 -1
  33. package/dist/views/CanvasEditor/BlockWrapper.js +24 -3
  34. package/dist/views/CanvasEditor/CanvasEditorView.d.ts.map +1 -1
  35. package/dist/views/CanvasEditor/CanvasEditorView.js +77 -17
  36. package/dist/views/CanvasEditor/EditorBody.d.ts.map +1 -1
  37. package/dist/views/CanvasEditor/EditorBody.js +1 -1
  38. package/dist/views/CanvasEditor/components/EditorCanvas.d.ts.map +1 -1
  39. package/dist/views/CanvasEditor/components/EditorCanvas.js +158 -100
  40. package/dist/views/CanvasEditor/components/EditorSidebar.d.ts +3 -1
  41. package/dist/views/CanvasEditor/components/EditorSidebar.d.ts.map +1 -1
  42. package/dist/views/CanvasEditor/components/EditorSidebar.js +3 -3
  43. package/dist/views/CanvasEditor/hooks/useRegisteredBlocks.d.ts +1 -1
  44. package/dist/views/CanvasEditor/hooks/useRegisteredBlocks.d.ts.map +1 -1
  45. package/dist/views/CanvasEditor/hooks/useRegisteredBlocks.js +6 -40
  46. package/dist/views/NewsletterManager.d.ts.map +1 -1
  47. package/dist/views/NewsletterManager.js +87 -5
  48. package/dist/views/components/DomainPromptModal.d.ts +13 -0
  49. package/dist/views/components/DomainPromptModal.d.ts.map +1 -0
  50. package/dist/views/components/DomainPromptModal.js +58 -0
  51. package/dist/views/components/NewsletterCard.d.ts +16 -0
  52. package/dist/views/components/NewsletterCard.d.ts.map +1 -0
  53. package/dist/views/components/NewsletterCard.js +94 -0
  54. package/dist/views/components/NewsletterGrid.d.ts +16 -0
  55. package/dist/views/components/NewsletterGrid.d.ts.map +1 -0
  56. package/dist/views/components/NewsletterGrid.js +13 -0
  57. package/dist/views/components/SendNewsletterModal.d.ts.map +1 -1
  58. package/dist/views/components/SendNewsletterModal.js +91 -22
  59. package/dist/views/components/SmtpSettingsModal.d.ts.map +1 -1
  60. package/dist/views/components/SmtpSettingsModal.js +10 -0
  61. package/dist/views/components/TestEmailModal.d.ts.map +1 -1
  62. package/dist/views/components/TestEmailModal.js +86 -17
  63. package/package.json +53 -9
  64. package/src/api/email-utils.ts +53 -4
  65. package/src/api/handlers/newsletters.ts +40 -20
  66. package/src/api/handlers/send-newsletter.ts +65 -6
  67. package/src/api/handlers/settings.ts +60 -2
  68. package/src/index.tsx +49 -155
  69. package/src/lib/blocks/BlockRenderer.tsx +16 -2
  70. package/src/lib/email/EmailRenderer.tsx +31 -20
  71. package/src/lib/utils/config-resolver.ts +71 -0
  72. package/src/registry/BlockRegistry.tsx +255 -0
  73. package/src/state/EditorContext.tsx +43 -8
  74. package/src/state/types.ts +16 -0
  75. package/src/types/block.ts +10 -0
  76. package/src/types/newsletter.ts +5 -0
  77. package/src/views/CanvasEditor/BlockWrapper.tsx +27 -2
  78. package/src/views/CanvasEditor/CanvasEditorView.tsx +142 -61
  79. package/src/views/CanvasEditor/EditorBody.tsx +17 -13
  80. package/src/views/CanvasEditor/components/EditorCanvas.tsx +178 -115
  81. package/src/views/CanvasEditor/components/EditorSidebar.tsx +57 -2
  82. package/src/views/CanvasEditor/hooks/useRegisteredBlocks.ts +6 -45
  83. package/src/views/NewsletterManager.tsx +164 -6
  84. package/src/views/components/DomainPromptModal.tsx +160 -0
  85. package/src/views/components/NewsletterCard.tsx +212 -0
  86. package/src/views/components/NewsletterGrid.tsx +48 -0
  87. package/src/views/components/SendNewsletterModal.tsx +270 -184
  88. package/src/views/components/SmtpSettingsModal.tsx +11 -0
  89. package/src/views/components/TestEmailModal.tsx +235 -149
  90. 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;AAqC9E,wBAAsB,gBAAgB,CAClC,MAAM,EAAE,mBAAmB,EAC3B,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,IAAI,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC,CAoHf"}
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"}
@@ -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
- const baseUrl = host
36
- ? (host.includes('localhost') ? 'http' : 'https') + '://' + host
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: isDutch ? 'Afmelden' : isSwedish ? 'Avanmälan' : 'Unsubscribe',
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,CAkEvB;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"}
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 listItems = newsletterList.map((newsletter) => ({
53
- id: newsletter._id?.toString() || newsletter.id,
54
- title: newsletter.title,
55
- slug: newsletter.slug,
56
- status: newsletter.publication?.status || 'draft',
57
- subject: newsletter.metadata?.subject || '',
58
- scheduledDate: newsletter.publication?.scheduledDate,
59
- sentDate: newsletter.publication?.sentDate,
60
- authorId: newsletter.publication?.authorId,
61
- updatedAt: newsletter.updatedAt || newsletter.createdAt,
62
- recipientCount: newsletter.recipientCount,
63
- hidden: newsletter.hidden,
64
- sendHistory: newsletter.sendHistory || [],
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: newsletter.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;AA4C7D,wBAAsB,oBAAoB,CACtC,GAAG,EAAE,WAAW,EAChB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,mBAAmB,GAC5B,OAAO,CAAC,YAAY,CAAC,CAwNvB;AAED,wBAAsB,uBAAuB,CACzC,GAAG,EAAE,WAAW,EAChB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,mBAAmB,GAC5B,OAAO,CAAC,YAAY,CAAC,CAuCvB"}
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
- const baseUrl = config.baseUrl || 'http://localhost:3001';
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
- const unsubscribeUrl = `${baseUrl}${subscriberSlug}?email=${encodeURIComponent(email)}`;
136
- return { email, unsubscribeUrl };
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: language,
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,CA2CvB;AAED,wBAAsB,kBAAkB,CACpC,GAAG,EAAE,WAAW,EAChB,MAAM,EAAE,mBAAmB,GAC5B,OAAO,CAAC,YAAY,CAAC,CAgDvB;AAwBD,wBAAsB,eAAe,CACjC,GAAG,EAAE,WAAW,EAChB,MAAM,EAAE,mBAAmB,GAC5B,OAAO,CAAC,YAAY,CAAC,CAoLvB"}
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
- const baseUrl = config.baseUrl || 'http://localhost:3001';
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, BlockTypeDefinition, ClientBlockDefinition, RichTextFormattingConfig, BlockEditProps, BlockPreviewProps, IBlockComponent, } from './types/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
@@ -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;AAOtD;;;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;CACL;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,OAAO,UAAU,gBAAgB,CAAC,KAAK,EAAE,WAAW,2CAiR1D;AAGD,OAAO,EAAE,gBAAgB,IAAI,KAAK,EAAE,CAAC;AAGrC,YAAY,EACR,KAAK,EACL,mBAAmB,EACnB,qBAAqB,EACrB,wBAAwB,EACxB,cAAc,EACd,iBAAiB,EACjB,eAAe,GAClB,MAAM,eAAe,CAAC;AAGvB,YAAY,EACR,UAAU,EACV,gBAAgB,EAChB,kBAAkB,EAClB,kBAAkB,EAClB,uBAAuB,GAC1B,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EAAE,oBAAoB,EAAE,MAAM,QAAQ,CAAC;AAC9C,YAAY,EAAE,sBAAsB,EAAE,MAAM,QAAQ,CAAC;AAGrD,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClE,YAAY,EAAE,mBAAmB,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAGpF,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,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"}