@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
@@ -20,6 +20,7 @@ interface SmtpConfig {
20
20
  from: string;
21
21
  fromName: string;
22
22
  logoUrl?: string;
23
+ unsubscribeTranslations?: Record<string, string>;
23
24
  }
24
25
 
25
26
  async function getSmtpConfig(config: NewsletterApiConfig): Promise<SmtpConfig | null> {
@@ -37,6 +38,7 @@ async function getSmtpConfig(config: NewsletterApiConfig): Promise<SmtpConfig |
37
38
  from: smtpConfig.from,
38
39
  fromName: smtpConfig.fromName || '',
39
40
  logoUrl: smtpConfig.logoUrl || '',
41
+ unsubscribeTranslations: smtpConfig.unsubscribeTranslations || {},
40
42
  };
41
43
  }
42
44
  return null;
@@ -49,6 +51,41 @@ function getNewsletterFilter(idOrSlug: string) {
49
51
  return { slug: idOrSlug };
50
52
  }
51
53
 
54
+ async function resolveBaseUrl(config: NewsletterApiConfig, language: string): Promise<string> {
55
+ try {
56
+ const dbConnection = await config.getDb();
57
+ const db = dbConnection.db();
58
+ const settings = db.collection('settings');
59
+
60
+ // Try to get site config from plugin-website (stored in 'settings' collection with identifier 'site_config')
61
+ const siteConfig = await settings.findOne({ identifier: 'site_config' });
62
+
63
+ if (siteConfig && siteConfig.domainLocaleConfig && Array.isArray(siteConfig.domainLocaleConfig)) {
64
+ // Find domain for this locale
65
+ const localeConfig = siteConfig.domainLocaleConfig.find((c: any) => c.locale === language);
66
+ if (localeConfig && localeConfig.domain && localeConfig.domain !== 'undefined' && localeConfig.domain.trim() !== '') {
67
+ const domain = localeConfig.domain.trim();
68
+ // Add protocol if missing
69
+ if (!domain.startsWith('http')) {
70
+ const protocol = domain.includes('localhost') ? 'http' : 'https';
71
+ return `${protocol}://${domain}`;
72
+ }
73
+ return domain;
74
+ }
75
+ }
76
+ } catch (error) {
77
+ console.warn('[NewsletterAPI] Failed to resolve language-specific base URL:', error);
78
+ }
79
+
80
+ // Fallback to default, carefully checking for 'undefined' string
81
+ const fallback = process.env.NEXT_PUBLIC_SITE_URL;
82
+ if (fallback && fallback !== 'undefined' && fallback.trim() !== '') {
83
+ return fallback;
84
+ }
85
+
86
+ return 'https://botanicsandyou.com';
87
+ }
88
+
52
89
  export async function POST_SEND_NEWSLETTER(
53
90
  req: NextRequest,
54
91
  idOrSlug: string,
@@ -123,7 +160,19 @@ export async function POST_SEND_NEWSLETTER(
123
160
  connectionTimeout: 30000,
124
161
  });
125
162
 
126
- const baseUrl = config.baseUrl || 'http://localhost:3001';
163
+ // Resolve base URL based on language settings from plugin-website
164
+ const baseUrl = await resolveBaseUrl(config, language);
165
+
166
+ // Final sanity check - if domain is STILL undefined, stop sending and ask user for domain
167
+ if (baseUrl.includes('undefined')) {
168
+ return NextResponse.json(
169
+ {
170
+ error: 'Domain not configured for this language. Please define your website domain first.',
171
+ code: 'DOMAIN_MISSING'
172
+ },
173
+ { status: 400 }
174
+ );
175
+ }
127
176
 
128
177
  let logoAttachment: any = undefined;
129
178
  let logoSrc = smtpConfig.logoUrl || `${baseUrl}/logo_black.svg`;
@@ -188,25 +237,35 @@ export async function POST_SEND_NEWSLETTER(
188
237
  const subscriber = isTest ? null : await subscribers.findOne({ email });
189
238
  const subscriberLang = subscriber?.language || language;
190
239
  const subscriberSlug = slugs[subscriberLang] || slugs.en;
191
- const unsubscribeUrl = `${baseUrl}${subscriberSlug}?email=${encodeURIComponent(email)}`;
192
240
 
193
- return { email, unsubscribeUrl };
241
+ // Resolve correct base URL for this specific subscriber's language
242
+ const subscriberBaseUrl = await resolveBaseUrl(config, subscriberLang);
243
+ const unsubscribeUrl = `${subscriberBaseUrl}${subscriberSlug}?email=${encodeURIComponent(email)}`;
244
+
245
+ return { email, unsubscribeUrl, subscriberLang, subscriberBaseUrl };
194
246
  })
195
247
  );
196
248
 
197
249
  let successCount = 0;
198
250
  let failedCount = 0;
199
251
 
200
- for (const { email, unsubscribeUrl } of recipientsWithUrls) {
252
+ for (const { email, unsubscribeUrl, subscriberLang, subscriberBaseUrl } of recipientsWithUrls) {
201
253
  try {
254
+ // Get unsubscribe text (Priority: metadata manual override > global SMTP translation > hardcoded localized default)
255
+ const globalTranslations = smtpConfig.unsubscribeTranslations || {};
256
+ const hardcodedDefaults: Record<string, string> = { en: 'Unsubscribe', nl: 'Afmelden', sv: 'Avanmälan' };
257
+ const defaultText = globalTranslations[subscriberLang] || hardcodedDefaults[subscriberLang] || hardcodedDefaults.en;
258
+ const unsubscribeText = metadata?.unsubscribeText || defaultText;
259
+
202
260
  const html = generateNewsletterEmailHtml(
203
261
  blocks,
204
262
  { subject: metadata.subject || '', previewText: metadata.previewText || '' },
205
263
  {
206
- baseUrl,
207
- locale: language,
264
+ baseUrl: subscriberBaseUrl,
265
+ locale: subscriberLang,
208
266
  logoUrl: logoSrc,
209
267
  unsubscribeUrl,
268
+ unsubscribeText,
210
269
  footerText: `© ${new Date().getFullYear()} ${smtpConfig.fromName || 'Botanics & You'}`,
211
270
  }
212
271
  );
@@ -120,6 +120,11 @@ export async function GET_SMTP_SETTINGS(
120
120
  fromName: '',
121
121
  primaryLanguage: 'en',
122
122
  logoUrl: '',
123
+ unsubscribeTranslations: {
124
+ en: 'Unsubscribe',
125
+ nl: 'Afmelden',
126
+ sv: 'Avanmälan',
127
+ },
123
128
  });
124
129
  }
125
130
 
@@ -132,6 +137,11 @@ export async function GET_SMTP_SETTINGS(
132
137
  fromName: smtpConfig.fromName || '',
133
138
  primaryLanguage: smtpConfig.primaryLanguage || 'en',
134
139
  logoUrl: smtpConfig.logoUrl || '',
140
+ unsubscribeTranslations: smtpConfig.unsubscribeTranslations || {
141
+ en: 'Unsubscribe',
142
+ nl: 'Afmelden',
143
+ sv: 'Avanmälan',
144
+ },
135
145
  });
136
146
  } catch (error: any) {
137
147
  console.error('[NewsletterAPI] GET_SMTP_SETTINGS error:', error);
@@ -178,6 +188,7 @@ export async function POST_SMTP_SETTINGS(
178
188
  fromName: body.fromName || '',
179
189
  primaryLanguage: body.primaryLanguage || 'en',
180
190
  logoUrl: body.logoUrl || '',
191
+ unsubscribeTranslations: body.unsubscribeTranslations || {},
181
192
  updatedAt: new Date(),
182
193
  updatedBy: userId,
183
194
  },
@@ -217,6 +228,41 @@ function getSmtpConfigFromDb(config: NewsletterApiConfig): Promise<{ host: strin
217
228
  })();
218
229
  }
219
230
 
231
+ async function resolveBaseUrl(config: NewsletterApiConfig, language: string): Promise<string> {
232
+ try {
233
+ const dbConnection = await config.getDb();
234
+ const db = dbConnection.db();
235
+ const settings = db.collection('settings');
236
+
237
+ // Try to get site config from plugin-website (stored in 'settings' collection with identifier 'site_config')
238
+ const siteConfig = await settings.findOne({ identifier: 'site_config' });
239
+
240
+ if (siteConfig && siteConfig.domainLocaleConfig && Array.isArray(siteConfig.domainLocaleConfig)) {
241
+ // Find domain for this locale
242
+ const localeConfig = siteConfig.domainLocaleConfig.find((c: any) => c.locale === language);
243
+ if (localeConfig && localeConfig.domain && localeConfig.domain !== 'undefined' && localeConfig.domain.trim() !== '') {
244
+ const domain = localeConfig.domain.trim();
245
+ // Add protocol if missing
246
+ if (!domain.startsWith('http')) {
247
+ const protocol = domain.includes('localhost') ? 'http' : 'https';
248
+ return `${protocol}://${domain}`;
249
+ }
250
+ return domain;
251
+ }
252
+ }
253
+ } catch (error) {
254
+ console.warn('[NewsletterAPI] Failed to resolve language-specific base URL:', error);
255
+ }
256
+
257
+ // Fallback to default, carefully checking for 'undefined' string
258
+ const fallback = process.env.NEXT_PUBLIC_SITE_URL;
259
+ if (fallback && fallback !== 'undefined' && fallback.trim() !== '') {
260
+ return fallback;
261
+ }
262
+
263
+ return 'https://botanicsandyou.com';
264
+ }
265
+
220
266
  export async function POST_TEST_EMAIL(
221
267
  req: NextRequest,
222
268
  config: NewsletterApiConfig
@@ -267,8 +313,20 @@ export async function POST_TEST_EMAIL(
267
313
  logoExists = fs.existsSync(altPath);
268
314
  }
269
315
 
270
- const baseUrl = config.baseUrl || 'http://localhost:3001';
271
-
316
+ // Resolve base URL based on language settings from plugin-website
317
+ const baseUrl = await resolveBaseUrl(config, language);
318
+
319
+ // Final sanity check - if domain is STILL undefined, stop sending and ask user for domain
320
+ if (baseUrl.includes('undefined')) {
321
+ return NextResponse.json(
322
+ {
323
+ error: 'Domain not configured for this language. Please define your website domain first.',
324
+ code: 'DOMAIN_MISSING'
325
+ },
326
+ { status: 400 }
327
+ );
328
+ }
329
+
272
330
  let logoAttachment: any = undefined;
273
331
  let logoSrc = smtpConfig.logoUrl || `${baseUrl}/logo_black.svg`;
274
332
 
package/src/index.tsx CHANGED
@@ -15,6 +15,7 @@ import { SettingsView } from './views/SettingsView';
15
15
  import { NewsletterManagerView } from './views/NewsletterManager';
16
16
  import { NewsletterEditorView } from './views/NewsletterEditor';
17
17
  import { editorStateToAPI } from './lib/mappers/apiMapper';
18
+ import { resolvePluginConfig } from './lib/utils/config-resolver';
18
19
 
19
20
  /**
20
21
  * Plugin Props Interface
@@ -35,109 +36,48 @@ export interface PluginProps {
35
36
  /** Background color for dark mode (optional) */
36
37
  dark?: string;
37
38
  };
39
+ /** Localized strings for modular blocks in the editor */
40
+ translations?: Record<string, any>;
41
+ /** Global translations for unsubscribe text */
42
+ unsubscribeTranslations?: Record<string, string>;
38
43
  }
39
44
 
45
+ /**
46
+ * Client-facing configuration type
47
+ * Allows partial overrides of plugin behavior
48
+ */
49
+ export type NewsletterPluginConfig = Partial<Omit<PluginProps, 'subPath' | 'siteId' | 'locale'>> & {
50
+ customBlocks?: ClientBlockDefinition[];
51
+ darkMode?: boolean;
52
+ backgroundColors?: {
53
+ light: string;
54
+ dark?: string;
55
+ };
56
+ translations?: Record<string, any>;
57
+ unsubscribeTranslations?: Record<string, string>;
58
+ emailConfig?: {
59
+ logoUrl?: string;
60
+ logoAlt?: string;
61
+ footerText?: string;
62
+ primaryColor?: string;
63
+ };
64
+ };
65
+
40
66
  /**
41
67
  * Main Router Component
42
68
  * Handles routing within the newsletter plugin
43
- *
44
- * Client Handshake:
45
- * - Client apps can pass customBlocks via props
46
- * - Or via window.__JHITS_PLUGIN_PROPS__['plugin-newsletter'].customBlocks
47
- * - The EditorProvider will automatically register these blocks
48
69
  */
49
70
  export default function NewsletterPlugin(props: PluginProps) {
50
- const { subPath, siteId, locale, customBlocks: propsCustomBlocks, darkMode: propsDarkMode, backgroundColors: propsBackgroundColors } = props;
51
-
52
- // Get custom blocks from props or window global (client app injection point)
53
- const customBlocks = useMemo(() => {
54
- // First, try props
55
- if (propsCustomBlocks && propsCustomBlocks.length > 0) {
56
- return propsCustomBlocks;
57
- }
58
-
59
- // Fallback to window global (for client app injection)
60
- if (typeof window !== 'undefined' && (window as any).__JHITS_PLUGIN_PROPS__) {
61
- const pluginProps = (window as any).__JHITS_PLUGIN_PROPS__['plugin-newsletter'];
62
- if (pluginProps?.customBlocks) {
63
- return pluginProps.customBlocks as ClientBlockDefinition[];
64
- }
65
- }
66
-
67
- return [];
68
- }, [propsCustomBlocks]);
69
-
70
- // Get dark mode setting from props, localStorage (dev settings), or window global
71
- // Priority: localStorage (dev) > props > window global > default
72
- const darkMode = useMemo(() => {
73
- // First, check localStorage for dev settings (highest priority for dev)
74
- if (typeof window !== 'undefined') {
75
- try {
76
- const saved = localStorage.getItem('__JHITS_PLUGIN_NEWSLETTER_CONFIG__');
77
- if (saved) {
78
- const config = JSON.parse(saved);
79
- if (config.darkMode !== undefined) {
80
- return config.darkMode as boolean;
81
- }
82
- }
83
- } catch (e) {
84
- // Ignore localStorage errors
85
- }
86
- }
87
-
88
- // Then try props
89
- if (propsDarkMode !== undefined) {
90
- return propsDarkMode;
91
- }
92
-
93
- // Fallback to window global if prop not provided
94
- if (typeof window !== 'undefined' && (window as any).__JHITS_PLUGIN_PROPS__) {
95
- const pluginProps = (window as any).__JHITS_PLUGIN_PROPS__['plugin-newsletter'];
96
- if (pluginProps?.darkMode !== undefined) {
97
- return pluginProps.darkMode as boolean;
98
- }
99
- }
100
-
101
- return true; // Default to dark mode enabled
102
- }, [propsDarkMode]);
103
-
104
- // Get background colors from props, localStorage (dev settings), or window global
105
- // Priority: localStorage (dev) > props > window global
106
- const backgroundColors = useMemo(() => {
107
- // First, check localStorage for dev settings (highest priority for dev)
108
- if (typeof window !== 'undefined') {
109
- try {
110
- const saved = localStorage.getItem('__JHITS_PLUGIN_NEWSLETTER_CONFIG__');
111
- if (saved) {
112
- const config = JSON.parse(saved);
113
- if (config.backgroundColors) {
114
- return config.backgroundColors;
115
- }
116
- }
117
- } catch (e) {
118
- // Ignore localStorage errors
119
- }
120
- }
121
-
122
- // Then try props
123
- if (propsBackgroundColors) {
124
- return propsBackgroundColors;
125
- }
126
-
127
- // Fallback to window global
128
- if (typeof window !== 'undefined' && (window as any).__JHITS_PLUGIN_PROPS__) {
129
- const pluginProps = (window as any).__JHITS_PLUGIN_PROPS__['plugin-newsletter'];
130
- if (pluginProps?.backgroundColors) {
131
- return pluginProps.backgroundColors as { light: string; dark?: string };
132
- }
133
- }
134
-
135
- return undefined;
136
- }, [propsBackgroundColors]);
71
+ const { subPath, siteId, locale } = props;
137
72
 
73
+ // Resolve configuration from multiple sources (Props, Window, Storage)
74
+ const config = useMemo(() => resolvePluginConfig(props), [props]);
75
+ const { customBlocks, darkMode, backgroundColors, translations, emailConfig, unsubscribeTranslations } = config;
76
+
138
77
  const route = subPath[0] || 'newsletters';
139
-
140
- // Listen for config updates from settings screen
78
+ const newsletterId = subPath[1];
79
+
80
+ // Listen for config updates (e.g. from settings screen)
141
81
  useEffect(() => {
142
82
  if (typeof window === 'undefined') return;
143
83
 
@@ -158,12 +98,14 @@ export default function NewsletterPlugin(props: PluginProps) {
158
98
  return <NewsletterManagerView siteId={siteId} locale={locale} />;
159
99
 
160
100
  case 'editor':
161
- const newsletterId = subPath[1];
162
101
  return (
163
102
  <EditorProvider
164
103
  customBlocks={customBlocks}
165
104
  darkMode={darkMode}
166
105
  backgroundColors={backgroundColors}
106
+ translations={translations}
107
+ emailConfig={emailConfig}
108
+ unsubscribeTranslations={unsubscribeTranslations}
167
109
  onSave={async (state, extraData?: { language?: string }) => {
168
110
  // Save to API - create new or update existing newsletter
169
111
  const originalId = newsletterId || state.slug;
@@ -180,7 +122,6 @@ export default function NewsletterPlugin(props: PluginProps) {
180
122
 
181
123
  // If we have an id, try to update first
182
124
  if (originalId) {
183
- console.log('[NewsletterPlugin] Attempting to update newsletter with id:', originalId);
184
125
  const updateResponse = await fetch(`/api/plugin-newsletter/newsletters/${originalId}${langParam}`, {
185
126
  method: 'PUT',
186
127
  headers: { 'Content-Type': 'application/json' },
@@ -190,32 +131,15 @@ export default function NewsletterPlugin(props: PluginProps) {
190
131
 
191
132
  if (updateResponse.ok) {
192
133
  const result = await updateResponse.json();
193
- // If the id changed, update the URL
194
134
  if (result.id && result.id !== originalId) {
195
135
  window.history.replaceState(null, '', `/dashboard/newsletter/editor/${result.id}`);
196
136
  }
197
137
  return result;
198
138
  }
199
-
200
- // If 404, newsletter doesn't exist, create a new one
201
- if (updateResponse.status === 404) {
202
- console.log('[NewsletterPlugin] Newsletter not found, creating new newsletter');
203
- } else {
204
- // Other error, throw it
205
- const error = await updateResponse.json();
206
- console.error('[NewsletterPlugin] Save failed:', {
207
- status: updateResponse.status,
208
- statusText: updateResponse.statusText,
209
- error,
210
- });
211
- const errorMessage = error.message || error.error || 'Failed to save newsletter';
212
- throw new Error(errorMessage);
213
- }
214
139
  }
215
140
 
216
- // Create new newsletter (either no id or update returned 404)
217
- console.log('[NewsletterPlugin] Creating new newsletter');
218
- const createResponse = await fetch('/api/plugin-newsletter/newsletters/new', {
141
+ // Create new newsletter
142
+ const createResponse = await fetch(`/api/plugin-newsletter/newsletters/new${langParam}`, {
219
143
  method: 'POST',
220
144
  headers: { 'Content-Type': 'application/json' },
221
145
  credentials: 'include',
@@ -224,17 +148,11 @@ export default function NewsletterPlugin(props: PluginProps) {
224
148
 
225
149
  if (!createResponse.ok) {
226
150
  const error = await createResponse.json();
227
- console.error('[NewsletterPlugin] Create failed:', {
228
- status: createResponse.status,
229
- statusText: createResponse.statusText,
230
- error,
231
- });
232
151
  const errorMessage = error.message || error.error || 'Failed to create newsletter';
233
152
  throw new Error(errorMessage);
234
153
  }
235
154
 
236
155
  const result = await createResponse.json();
237
- // Update the URL to the new newsletter's id
238
156
  if (result.id) {
239
157
  window.history.replaceState(null, '', `/dashboard/newsletter/editor/${result.id}`);
240
158
  }
@@ -251,11 +169,11 @@ export default function NewsletterPlugin(props: PluginProps) {
251
169
  customBlocks={customBlocks}
252
170
  darkMode={darkMode}
253
171
  backgroundColors={backgroundColors}
172
+ translations={translations}
173
+ emailConfig={emailConfig}
174
+ unsubscribeTranslations={unsubscribeTranslations}
254
175
  onSave={async (state, extraData?: { language?: string }) => {
255
- // Save to API - create new newsletter
256
176
  const apiData = editorStateToAPI(state);
257
-
258
- // Include language in metadata if provided
259
177
  if (extraData?.language) {
260
178
  apiData.metadata = apiData.metadata || {};
261
179
  apiData.metadata.lang = extraData.language;
@@ -272,7 +190,6 @@ export default function NewsletterPlugin(props: PluginProps) {
272
190
  throw new Error(error.message || 'Failed to create newsletter');
273
191
  }
274
192
  const result = await response.json();
275
- // Update the URL to the new newsletter's slug
276
193
  if (result.slug) {
277
194
  window.history.replaceState(null, '', `/dashboard/newsletter/editor/${result.slug}`);
278
195
  }
@@ -292,6 +209,9 @@ export default function NewsletterPlugin(props: PluginProps) {
292
209
  customBlocks={customBlocks}
293
210
  darkMode={darkMode}
294
211
  backgroundColors={backgroundColors}
212
+ translations={translations}
213
+ emailConfig={emailConfig}
214
+ unsubscribeTranslations={unsubscribeTranslations}
295
215
  isWelcomeEmail={true}
296
216
  onSave={async (state, extraData?: { language?: string }) => {
297
217
  const apiData = editorStateToAPI(state);
@@ -321,36 +241,10 @@ export default function NewsletterPlugin(props: PluginProps) {
321
241
  }
322
242
  }
323
243
 
324
- // Export for use as default
244
+ // Export symbols as needed
325
245
  export { NewsletterPlugin as Index };
326
-
327
- // Export types for client applications
328
- export type {
329
- Block,
330
- BlockTypeDefinition,
331
- ClientBlockDefinition,
332
- RichTextFormattingConfig,
333
- BlockEditProps,
334
- BlockPreviewProps,
335
- IBlockComponent,
336
- } from './types/block';
337
-
338
- // Export newsletter types
339
- export type {
340
- Newsletter,
341
- NewsletterStatus,
342
- NewsletterMetadata,
343
- NewsletterListItem,
344
- NewsletterFilterOptions,
345
- } from './types/newsletter';
346
-
347
- // Export initialization utility for easy setup
246
+ export type { Block, ClientBlockDefinition } from './types/block';
348
247
  export { initNewsletterPlugin } from './init';
349
- export type { NewsletterPluginConfig } from './init';
350
-
351
- // Export editor state management
352
- export { EditorProvider, useEditor } from './state/EditorContext';
353
- export type { EditorProviderProps, EditorState, EditorContextValue } from './state';
354
-
355
- // Export block registry
356
248
  export { blockRegistry } from './registry';
249
+ export { EditorProvider, useEditor } from './state/EditorContext';
250
+ export { BlockRenderer, BlocksRenderer } from './lib/blocks/BlockRenderer';
@@ -12,6 +12,7 @@ import React from 'react';
12
12
  import { Block, BlockPreviewProps } from '../../types/block';
13
13
  import { blockRegistry } from '../../registry/BlockRegistry';
14
14
  import { getChildBlocks } from '../utils/blockHelpers';
15
+ import { useEditor } from '../../state/EditorContext';
15
16
 
16
17
  /**
17
18
  * Block Renderer Props
@@ -40,6 +41,13 @@ export function BlockRenderer({
40
41
  customRenderers,
41
42
  context = {}
42
43
  }: BlockRendererProps) {
44
+ // We access the context to ensure re-render when blocks are registered
45
+ try {
46
+ useEditor();
47
+ } catch (e) {
48
+ // Not in editor context (e.g. preview bridge)
49
+ }
50
+
43
51
  // Check for custom renderer override first
44
52
  if (customRenderers?.has(block.type)) {
45
53
  const CustomRenderer = customRenderers.get(block.type)!;
@@ -47,9 +55,15 @@ export function BlockRenderer({
47
55
  }
48
56
 
49
57
  // Get block definition from registry
50
- const definition = blockRegistry.get(block.type);
58
+ let definition = blockRegistry.get(block.type);
59
+
60
+ // If not found, try one last immediate re-check of the global window registry
61
+ if (!definition && typeof window !== 'undefined' && (window as any).__JHITS_NEWSLETTER_REGISTRY__) {
62
+ definition = (window as any).__JHITS_NEWSLETTER_REGISTRY__.get(block.type);
63
+ }
64
+
51
65
  if (!definition) {
52
- console.warn(`Block type "${block.type}" not found in registry. Available types:`,
66
+ console.warn(`[BlockRenderer] Unknown block type: ${block.type}. Registry contains:`,
53
67
  blockRegistry.getAll().map(b => b.type).join(', '));
54
68
  return (
55
69
  <div className="p-4 border border-red-300 bg-red-50 rounded">