@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.
- package/dist/api/email-utils.d.ts.map +1 -1
- package/dist/api/email-utils.js +45 -4
- package/dist/api/handlers/send-newsletter.d.ts.map +1 -1
- package/dist/api/handlers/send-newsletter.js +54 -6
- package/dist/api/handlers/settings.d.ts.map +1 -1
- package/dist/api/handlers/settings.js +51 -1
- package/dist/index.d.ts +27 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -122
- package/dist/lib/blocks/BlockRenderer.d.ts.map +1 -1
- package/dist/lib/blocks/BlockRenderer.js +14 -2
- package/dist/lib/email/EmailRenderer.d.ts +1 -0
- package/dist/lib/email/EmailRenderer.d.ts.map +1 -1
- package/dist/lib/email/EmailRenderer.js +31 -19
- package/dist/lib/utils/config-resolver.d.ts +33 -0
- package/dist/lib/utils/config-resolver.d.ts.map +1 -0
- package/dist/lib/utils/config-resolver.js +47 -0
- package/dist/registry/BlockRegistry.d.ts +9 -1
- package/dist/registry/BlockRegistry.d.ts.map +1 -1
- package/dist/registry/BlockRegistry.js +126 -8
- package/dist/state/EditorContext.d.ts +11 -1
- package/dist/state/EditorContext.d.ts.map +1 -1
- package/dist/state/EditorContext.js +23 -5
- package/dist/state/types.d.ts +12 -0
- package/dist/state/types.d.ts.map +1 -1
- package/dist/types/block.d.ts +9 -0
- package/dist/types/block.d.ts.map +1 -1
- package/dist/types/newsletter.d.ts +2 -0
- package/dist/types/newsletter.d.ts.map +1 -1
- package/dist/views/CanvasEditor/BlockWrapper.d.ts.map +1 -1
- package/dist/views/CanvasEditor/BlockWrapper.js +24 -3
- package/dist/views/CanvasEditor/CanvasEditorView.d.ts.map +1 -1
- package/dist/views/CanvasEditor/CanvasEditorView.js +77 -17
- package/dist/views/CanvasEditor/EditorBody.d.ts.map +1 -1
- package/dist/views/CanvasEditor/EditorBody.js +1 -1
- package/dist/views/CanvasEditor/components/EditorCanvas.d.ts.map +1 -1
- package/dist/views/CanvasEditor/components/EditorCanvas.js +158 -100
- package/dist/views/CanvasEditor/components/EditorSidebar.d.ts +3 -1
- package/dist/views/CanvasEditor/components/EditorSidebar.d.ts.map +1 -1
- package/dist/views/CanvasEditor/components/EditorSidebar.js +3 -3
- package/dist/views/CanvasEditor/hooks/useRegisteredBlocks.d.ts +1 -1
- package/dist/views/CanvasEditor/hooks/useRegisteredBlocks.d.ts.map +1 -1
- package/dist/views/CanvasEditor/hooks/useRegisteredBlocks.js +6 -40
- package/dist/views/components/DomainPromptModal.d.ts +13 -0
- package/dist/views/components/DomainPromptModal.d.ts.map +1 -0
- package/dist/views/components/DomainPromptModal.js +58 -0
- package/dist/views/components/SendNewsletterModal.d.ts.map +1 -1
- package/dist/views/components/SendNewsletterModal.js +91 -22
- package/dist/views/components/SmtpSettingsModal.d.ts.map +1 -1
- package/dist/views/components/SmtpSettingsModal.js +10 -0
- package/dist/views/components/TestEmailModal.d.ts.map +1 -1
- package/dist/views/components/TestEmailModal.js +86 -17
- package/package.json +53 -9
- package/src/api/email-utils.ts +53 -4
- package/src/api/handlers/send-newsletter.ts +65 -6
- package/src/api/handlers/settings.ts +60 -2
- package/src/index.tsx +49 -155
- package/src/lib/blocks/BlockRenderer.tsx +16 -2
- package/src/lib/email/EmailRenderer.tsx +31 -20
- package/src/lib/utils/config-resolver.ts +71 -0
- package/src/registry/BlockRegistry.tsx +255 -0
- package/src/state/EditorContext.tsx +43 -8
- package/src/state/types.ts +16 -0
- package/src/types/block.ts +10 -0
- package/src/types/newsletter.ts +3 -0
- package/src/views/CanvasEditor/BlockWrapper.tsx +27 -2
- package/src/views/CanvasEditor/CanvasEditorView.tsx +142 -61
- package/src/views/CanvasEditor/EditorBody.tsx +17 -13
- package/src/views/CanvasEditor/components/EditorCanvas.tsx +178 -115
- package/src/views/CanvasEditor/components/EditorSidebar.tsx +57 -2
- package/src/views/CanvasEditor/hooks/useRegisteredBlocks.ts +6 -45
- package/src/views/components/DomainPromptModal.tsx +160 -0
- package/src/views/components/SendNewsletterModal.tsx +270 -184
- package/src/views/components/SmtpSettingsModal.tsx +11 -0
- package/src/views/components/TestEmailModal.tsx +235 -149
- package/src/registry/BlockRegistry.ts +0 -53
|
@@ -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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
217
|
-
|
|
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
|
|
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
|
-
|
|
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(`
|
|
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">
|