@jhits/plugin-newsletter 0.0.16 → 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 (76) 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/send-newsletter.d.ts.map +1 -1
  4. package/dist/api/handlers/send-newsletter.js +54 -6
  5. package/dist/api/handlers/settings.d.ts.map +1 -1
  6. package/dist/api/handlers/settings.js +51 -1
  7. package/dist/index.d.ts +27 -10
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +15 -122
  10. package/dist/lib/blocks/BlockRenderer.d.ts.map +1 -1
  11. package/dist/lib/blocks/BlockRenderer.js +14 -2
  12. package/dist/lib/email/EmailRenderer.d.ts +1 -0
  13. package/dist/lib/email/EmailRenderer.d.ts.map +1 -1
  14. package/dist/lib/email/EmailRenderer.js +31 -19
  15. package/dist/lib/utils/config-resolver.d.ts +33 -0
  16. package/dist/lib/utils/config-resolver.d.ts.map +1 -0
  17. package/dist/lib/utils/config-resolver.js +47 -0
  18. package/dist/registry/BlockRegistry.d.ts +9 -1
  19. package/dist/registry/BlockRegistry.d.ts.map +1 -1
  20. package/dist/registry/BlockRegistry.js +126 -8
  21. package/dist/state/EditorContext.d.ts +11 -1
  22. package/dist/state/EditorContext.d.ts.map +1 -1
  23. package/dist/state/EditorContext.js +23 -5
  24. package/dist/state/types.d.ts +12 -0
  25. package/dist/state/types.d.ts.map +1 -1
  26. package/dist/types/block.d.ts +9 -0
  27. package/dist/types/block.d.ts.map +1 -1
  28. package/dist/types/newsletter.d.ts +2 -0
  29. package/dist/types/newsletter.d.ts.map +1 -1
  30. package/dist/views/CanvasEditor/BlockWrapper.d.ts.map +1 -1
  31. package/dist/views/CanvasEditor/BlockWrapper.js +24 -3
  32. package/dist/views/CanvasEditor/CanvasEditorView.d.ts.map +1 -1
  33. package/dist/views/CanvasEditor/CanvasEditorView.js +77 -17
  34. package/dist/views/CanvasEditor/EditorBody.d.ts.map +1 -1
  35. package/dist/views/CanvasEditor/EditorBody.js +1 -1
  36. package/dist/views/CanvasEditor/components/EditorCanvas.d.ts.map +1 -1
  37. package/dist/views/CanvasEditor/components/EditorCanvas.js +158 -100
  38. package/dist/views/CanvasEditor/components/EditorSidebar.d.ts +3 -1
  39. package/dist/views/CanvasEditor/components/EditorSidebar.d.ts.map +1 -1
  40. package/dist/views/CanvasEditor/components/EditorSidebar.js +3 -3
  41. package/dist/views/CanvasEditor/hooks/useRegisteredBlocks.d.ts +1 -1
  42. package/dist/views/CanvasEditor/hooks/useRegisteredBlocks.d.ts.map +1 -1
  43. package/dist/views/CanvasEditor/hooks/useRegisteredBlocks.js +6 -40
  44. package/dist/views/components/DomainPromptModal.d.ts +13 -0
  45. package/dist/views/components/DomainPromptModal.d.ts.map +1 -0
  46. package/dist/views/components/DomainPromptModal.js +58 -0
  47. package/dist/views/components/SendNewsletterModal.d.ts.map +1 -1
  48. package/dist/views/components/SendNewsletterModal.js +91 -22
  49. package/dist/views/components/SmtpSettingsModal.d.ts.map +1 -1
  50. package/dist/views/components/SmtpSettingsModal.js +10 -0
  51. package/dist/views/components/TestEmailModal.d.ts.map +1 -1
  52. package/dist/views/components/TestEmailModal.js +86 -17
  53. package/package.json +53 -9
  54. package/src/api/email-utils.ts +53 -4
  55. package/src/api/handlers/send-newsletter.ts +65 -6
  56. package/src/api/handlers/settings.ts +60 -2
  57. package/src/index.tsx +49 -155
  58. package/src/lib/blocks/BlockRenderer.tsx +16 -2
  59. package/src/lib/email/EmailRenderer.tsx +31 -20
  60. package/src/lib/utils/config-resolver.ts +71 -0
  61. package/src/registry/BlockRegistry.tsx +255 -0
  62. package/src/state/EditorContext.tsx +43 -8
  63. package/src/state/types.ts +16 -0
  64. package/src/types/block.ts +10 -0
  65. package/src/types/newsletter.ts +3 -0
  66. package/src/views/CanvasEditor/BlockWrapper.tsx +27 -2
  67. package/src/views/CanvasEditor/CanvasEditorView.tsx +142 -61
  68. package/src/views/CanvasEditor/EditorBody.tsx +17 -13
  69. package/src/views/CanvasEditor/components/EditorCanvas.tsx +178 -115
  70. package/src/views/CanvasEditor/components/EditorSidebar.tsx +57 -2
  71. package/src/views/CanvasEditor/hooks/useRegisteredBlocks.ts +6 -45
  72. package/src/views/components/DomainPromptModal.tsx +160 -0
  73. package/src/views/components/SendNewsletterModal.tsx +270 -184
  74. package/src/views/components/SmtpSettingsModal.tsx +11 -0
  75. package/src/views/components/TestEmailModal.tsx +235 -149
  76. 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":"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"}
package/dist/index.js CHANGED
@@ -13,96 +13,19 @@ import { SettingsView } from './views/SettingsView';
13
13
  import { NewsletterManagerView } from './views/NewsletterManager';
14
14
  import { NewsletterEditorView } from './views/NewsletterEditor';
15
15
  import { editorStateToAPI } from './lib/mappers/apiMapper';
16
+ import { resolvePluginConfig } from './lib/utils/config-resolver';
16
17
  /**
17
18
  * Main Router Component
18
19
  * Handles routing within the newsletter plugin
19
- *
20
- * Client Handshake:
21
- * - Client apps can pass customBlocks via props
22
- * - Or via window.__JHITS_PLUGIN_PROPS__['plugin-newsletter'].customBlocks
23
- * - The EditorProvider will automatically register these blocks
24
20
  */
25
21
  export default function NewsletterPlugin(props) {
26
- const { subPath, siteId, locale, customBlocks: propsCustomBlocks, darkMode: propsDarkMode, backgroundColors: propsBackgroundColors } = props;
27
- // Get custom blocks from props or window global (client app injection point)
28
- const customBlocks = useMemo(() => {
29
- // First, try props
30
- if (propsCustomBlocks && propsCustomBlocks.length > 0) {
31
- return propsCustomBlocks;
32
- }
33
- // Fallback to window global (for client app injection)
34
- if (typeof window !== 'undefined' && window.__JHITS_PLUGIN_PROPS__) {
35
- const pluginProps = window.__JHITS_PLUGIN_PROPS__['plugin-newsletter'];
36
- if (pluginProps?.customBlocks) {
37
- return pluginProps.customBlocks;
38
- }
39
- }
40
- return [];
41
- }, [propsCustomBlocks]);
42
- // Get dark mode setting from props, localStorage (dev settings), or window global
43
- // Priority: localStorage (dev) > props > window global > default
44
- const darkMode = useMemo(() => {
45
- // First, check localStorage for dev settings (highest priority for dev)
46
- if (typeof window !== 'undefined') {
47
- try {
48
- const saved = localStorage.getItem('__JHITS_PLUGIN_NEWSLETTER_CONFIG__');
49
- if (saved) {
50
- const config = JSON.parse(saved);
51
- if (config.darkMode !== undefined) {
52
- return config.darkMode;
53
- }
54
- }
55
- }
56
- catch (e) {
57
- // Ignore localStorage errors
58
- }
59
- }
60
- // Then try props
61
- if (propsDarkMode !== undefined) {
62
- return propsDarkMode;
63
- }
64
- // Fallback to window global if prop not provided
65
- if (typeof window !== 'undefined' && window.__JHITS_PLUGIN_PROPS__) {
66
- const pluginProps = window.__JHITS_PLUGIN_PROPS__['plugin-newsletter'];
67
- if (pluginProps?.darkMode !== undefined) {
68
- return pluginProps.darkMode;
69
- }
70
- }
71
- return true; // Default to dark mode enabled
72
- }, [propsDarkMode]);
73
- // Get background colors from props, localStorage (dev settings), or window global
74
- // Priority: localStorage (dev) > props > window global
75
- const backgroundColors = useMemo(() => {
76
- // First, check localStorage for dev settings (highest priority for dev)
77
- if (typeof window !== 'undefined') {
78
- try {
79
- const saved = localStorage.getItem('__JHITS_PLUGIN_NEWSLETTER_CONFIG__');
80
- if (saved) {
81
- const config = JSON.parse(saved);
82
- if (config.backgroundColors) {
83
- return config.backgroundColors;
84
- }
85
- }
86
- }
87
- catch (e) {
88
- // Ignore localStorage errors
89
- }
90
- }
91
- // Then try props
92
- if (propsBackgroundColors) {
93
- return propsBackgroundColors;
94
- }
95
- // Fallback to window global
96
- if (typeof window !== 'undefined' && window.__JHITS_PLUGIN_PROPS__) {
97
- const pluginProps = window.__JHITS_PLUGIN_PROPS__['plugin-newsletter'];
98
- if (pluginProps?.backgroundColors) {
99
- return pluginProps.backgroundColors;
100
- }
101
- }
102
- return undefined;
103
- }, [propsBackgroundColors]);
22
+ const { subPath, siteId, locale } = props;
23
+ // Resolve configuration from multiple sources (Props, Window, Storage)
24
+ const config = useMemo(() => resolvePluginConfig(props), [props]);
25
+ const { customBlocks, darkMode, backgroundColors, translations, emailConfig, unsubscribeTranslations } = config;
104
26
  const route = subPath[0] || 'newsletters';
105
- // Listen for config updates from settings screen
27
+ const newsletterId = subPath[1];
28
+ // Listen for config updates (e.g. from settings screen)
106
29
  useEffect(() => {
107
30
  if (typeof window === 'undefined')
108
31
  return;
@@ -120,8 +43,7 @@ export default function NewsletterPlugin(props) {
120
43
  case 'newsletters':
121
44
  return _jsx(NewsletterManagerView, { siteId: siteId, locale: locale });
122
45
  case 'editor':
123
- const newsletterId = subPath[1];
124
- return (_jsx(EditorProvider, { customBlocks: customBlocks, darkMode: darkMode, backgroundColors: backgroundColors, onSave: async (state, extraData) => {
46
+ return (_jsx(EditorProvider, { customBlocks: customBlocks, darkMode: darkMode, backgroundColors: backgroundColors, translations: translations, emailConfig: emailConfig, unsubscribeTranslations: unsubscribeTranslations, onSave: async (state, extraData) => {
125
47
  // Save to API - create new or update existing newsletter
126
48
  const originalId = newsletterId || state.slug;
127
49
  const apiData = editorStateToAPI(state);
@@ -134,7 +56,6 @@ export default function NewsletterPlugin(props) {
134
56
  const langParam = extraData?.language ? `?language=${extraData.language}` : '';
135
57
  // If we have an id, try to update first
136
58
  if (originalId) {
137
- console.log('[NewsletterPlugin] Attempting to update newsletter with id:', originalId);
138
59
  const updateResponse = await fetch(`/api/plugin-newsletter/newsletters/${originalId}${langParam}`, {
139
60
  method: 'PUT',
140
61
  headers: { 'Content-Type': 'application/json' },
@@ -143,31 +64,14 @@ export default function NewsletterPlugin(props) {
143
64
  });
144
65
  if (updateResponse.ok) {
145
66
  const result = await updateResponse.json();
146
- // If the id changed, update the URL
147
67
  if (result.id && result.id !== originalId) {
148
68
  window.history.replaceState(null, '', `/dashboard/newsletter/editor/${result.id}`);
149
69
  }
150
70
  return result;
151
71
  }
152
- // If 404, newsletter doesn't exist, create a new one
153
- if (updateResponse.status === 404) {
154
- console.log('[NewsletterPlugin] Newsletter not found, creating new newsletter');
155
- }
156
- else {
157
- // Other error, throw it
158
- const error = await updateResponse.json();
159
- console.error('[NewsletterPlugin] Save failed:', {
160
- status: updateResponse.status,
161
- statusText: updateResponse.statusText,
162
- error,
163
- });
164
- const errorMessage = error.message || error.error || 'Failed to save newsletter';
165
- throw new Error(errorMessage);
166
- }
167
72
  }
168
- // Create new newsletter (either no id or update returned 404)
169
- console.log('[NewsletterPlugin] Creating new newsletter');
170
- const createResponse = await fetch('/api/plugin-newsletter/newsletters/new', {
73
+ // Create new newsletter
74
+ const createResponse = await fetch(`/api/plugin-newsletter/newsletters/new${langParam}`, {
171
75
  method: 'POST',
172
76
  headers: { 'Content-Type': 'application/json' },
173
77
  credentials: 'include',
@@ -175,26 +79,18 @@ export default function NewsletterPlugin(props) {
175
79
  });
176
80
  if (!createResponse.ok) {
177
81
  const error = await createResponse.json();
178
- console.error('[NewsletterPlugin] Create failed:', {
179
- status: createResponse.status,
180
- statusText: createResponse.statusText,
181
- error,
182
- });
183
82
  const errorMessage = error.message || error.error || 'Failed to create newsletter';
184
83
  throw new Error(errorMessage);
185
84
  }
186
85
  const result = await createResponse.json();
187
- // Update the URL to the new newsletter's id
188
86
  if (result.id) {
189
87
  window.history.replaceState(null, '', `/dashboard/newsletter/editor/${result.id}`);
190
88
  }
191
89
  return result;
192
90
  }, children: _jsx(NewsletterEditorView, { newsletterId: newsletterId, siteId: siteId, locale: locale, darkMode: darkMode, backgroundColors: backgroundColors }) }));
193
91
  case 'new':
194
- return (_jsx(EditorProvider, { customBlocks: customBlocks, darkMode: darkMode, backgroundColors: backgroundColors, onSave: async (state, extraData) => {
195
- // Save to API - create new newsletter
92
+ return (_jsx(EditorProvider, { customBlocks: customBlocks, darkMode: darkMode, backgroundColors: backgroundColors, translations: translations, emailConfig: emailConfig, unsubscribeTranslations: unsubscribeTranslations, onSave: async (state, extraData) => {
196
93
  const apiData = editorStateToAPI(state);
197
- // Include language in metadata if provided
198
94
  if (extraData?.language) {
199
95
  apiData.metadata = apiData.metadata || {};
200
96
  apiData.metadata.lang = extraData.language;
@@ -210,7 +106,6 @@ export default function NewsletterPlugin(props) {
210
106
  throw new Error(error.message || 'Failed to create newsletter');
211
107
  }
212
108
  const result = await response.json();
213
- // Update the URL to the new newsletter's slug
214
109
  if (result.slug) {
215
110
  window.history.replaceState(null, '', `/dashboard/newsletter/editor/${result.slug}`);
216
111
  }
@@ -219,7 +114,7 @@ export default function NewsletterPlugin(props) {
219
114
  case 'subscribers':
220
115
  return _jsx(SubscribersView, { siteId: siteId, locale: locale });
221
116
  case 'welcome':
222
- return (_jsx(EditorProvider, { customBlocks: customBlocks, darkMode: darkMode, backgroundColors: backgroundColors, isWelcomeEmail: true, onSave: async (state, extraData) => {
117
+ return (_jsx(EditorProvider, { customBlocks: customBlocks, darkMode: darkMode, backgroundColors: backgroundColors, translations: translations, emailConfig: emailConfig, unsubscribeTranslations: unsubscribeTranslations, isWelcomeEmail: true, onSave: async (state, extraData) => {
223
118
  const apiData = editorStateToAPI(state);
224
119
  const language = extraData?.language || locale || 'en';
225
120
  const response = await fetch(`/api/plugin-newsletter/welcome-email?language=${language}`, {
@@ -240,11 +135,9 @@ export default function NewsletterPlugin(props) {
240
135
  return _jsx(NewsletterManagerView, { siteId: siteId, locale: locale });
241
136
  }
242
137
  }
243
- // Export for use as default
138
+ // Export symbols as needed
244
139
  export { NewsletterPlugin as Index };
245
- // Export initialization utility for easy setup
246
140
  export { initNewsletterPlugin } from './init';
247
- // Export editor state management
248
- export { EditorProvider, useEditor } from './state/EditorContext';
249
- // Export block registry
250
141
  export { blockRegistry } from './registry';
142
+ export { EditorProvider, useEditor } from './state/EditorContext';
143
+ export { BlockRenderer, BlocksRenderer } from './lib/blocks/BlockRenderer';
@@ -1 +1 @@
1
- {"version":3,"file":"BlockRenderer.d.ts","sourceRoot":"","sources":["../../../src/lib/blocks/BlockRenderer.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAI7D;;GAEG;AACH,MAAM,WAAW,kBAAkB;IAC/B,sBAAsB;IACtB,KAAK,EAAE,KAAK,CAAC;IAEb,oEAAoE;IACpE,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,aAAa,CAAC,iBAAiB,CAAC,CAAC,CAAC;IAEtE,uCAAuC;IACvC,OAAO,CAAC,EAAE;QACN,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KAC1B,CAAC;CACL;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,EAC1B,KAAK,EACL,eAAe,EACf,OAAY,EACf,EAAE,kBAAkB,2CAoDpB;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,EAC3B,MAAM,EACN,eAAe,EACf,OAAY,EACf,EAAE;IACC,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,aAAa,CAAC,iBAAiB,CAAC,CAAC,CAAC;IACtE,OAAO,CAAC,EAAE;QACN,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KAC1B,CAAC;CACL,2CAaA"}
1
+ {"version":3,"file":"BlockRenderer.d.ts","sourceRoot":"","sources":["../../../src/lib/blocks/BlockRenderer.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAK7D;;GAEG;AACH,MAAM,WAAW,kBAAkB;IAC/B,sBAAsB;IACtB,KAAK,EAAE,KAAK,CAAC;IAEb,oEAAoE;IACpE,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,aAAa,CAAC,iBAAiB,CAAC,CAAC,CAAC;IAEtE,uCAAuC;IACvC,OAAO,CAAC,EAAE;QACN,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KAC1B,CAAC;CACL;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,EAC1B,KAAK,EACL,eAAe,EACf,OAAY,EACf,EAAE,kBAAkB,2CAiEpB;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,EAC3B,MAAM,EACN,eAAe,EACf,OAAY,EACf,EAAE;IACC,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,aAAa,CAAC,iBAAiB,CAAC,CAAC,CAAC;IACtE,OAAO,CAAC,EAAE;QACN,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KAC1B,CAAC;CACL,2CAaA"}
@@ -8,20 +8,32 @@
8
8
  'use client';
9
9
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
10
10
  import { blockRegistry } from '../../registry/BlockRegistry';
11
+ import { useEditor } from '../../state/EditorContext';
11
12
  /**
12
13
  * Block Renderer Component
13
14
  * Renders a single block using its Preview component from the registry
14
15
  */
15
16
  export function BlockRenderer({ block, customRenderers, context = {} }) {
17
+ // We access the context to ensure re-render when blocks are registered
18
+ try {
19
+ useEditor();
20
+ }
21
+ catch (e) {
22
+ // Not in editor context (e.g. preview bridge)
23
+ }
16
24
  // Check for custom renderer override first
17
25
  if (customRenderers?.has(block.type)) {
18
26
  const CustomRenderer = customRenderers.get(block.type);
19
27
  return _jsx(CustomRenderer, { block: block, context: context });
20
28
  }
21
29
  // Get block definition from registry
22
- const definition = blockRegistry.get(block.type);
30
+ let definition = blockRegistry.get(block.type);
31
+ // If not found, try one last immediate re-check of the global window registry
32
+ if (!definition && typeof window !== 'undefined' && window.__JHITS_NEWSLETTER_REGISTRY__) {
33
+ definition = window.__JHITS_NEWSLETTER_REGISTRY__.get(block.type);
34
+ }
23
35
  if (!definition) {
24
- console.warn(`Block type "${block.type}" not found in registry. Available types:`, blockRegistry.getAll().map(b => b.type).join(', '));
36
+ console.warn(`[BlockRenderer] Unknown block type: ${block.type}. Registry contains:`, blockRegistry.getAll().map(b => b.type).join(', '));
25
37
  return (_jsxs("div", { className: "p-4 border border-red-300 bg-red-50 rounded", children: [_jsxs("p", { className: "text-red-600", children: ["Unknown block type: ", block.type] }), _jsx("p", { className: "text-xs text-red-500 mt-1", children: "Make sure this block type is registered via customBlocks prop" })] }));
26
38
  }
27
39
  // Use the Preview component from the block definition
@@ -39,6 +39,7 @@ export declare function generateNewsletterEmailHtml(blocks: Block[], metadata: {
39
39
  previewText?: string;
40
40
  }, context: EmailRenderContext & {
41
41
  unsubscribeUrl?: string;
42
+ unsubscribeText?: string;
42
43
  footerText?: string;
43
44
  logoUrl?: string;
44
45
  logoAlt?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"EmailRenderer.d.ts","sourceRoot":"","sources":["../../../src/lib/email/EmailRenderer.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAI1C;;GAEG;AACH,MAAM,WAAW,kBAAkB;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CAC1B;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,GAAE,kBAAuB,GAAG,MAAM,CAmBzF;AA0OD;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,OAAO,GAAE,kBAAuB,GAAG,MAAM,CAE7F;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CACvC,MAAM,EAAE,KAAK,EAAE,EACf,QAAQ,EAAE;IACN,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB,EACD,OAAO,EAAE,kBAAkB,GAAG;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB,GACF,MAAM,CA8GR"}
1
+ {"version":3,"file":"EmailRenderer.d.ts","sourceRoot":"","sources":["../../../src/lib/email/EmailRenderer.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAG1C;;GAEG;AACH,MAAM,WAAW,kBAAkB;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CAC1B;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,GAAE,kBAAuB,GAAG,MAAM,CAmBzF;AAoPD;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,OAAO,GAAE,kBAAuB,GAAG,MAAM,CAE7F;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CACvC,MAAM,EAAE,KAAK,EAAE,EACf,QAAQ,EAAE;IACN,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB,EACD,OAAO,EAAE,kBAAkB,GAAG;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB,GACF,MAAM,CA+GR"}