@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,23 +1,33 @@
1
1
  'use client';
2
2
 
3
- import React, { useRef, useEffect, useState } from 'react';
3
+ import React, { useRef, useEffect, useState, useMemo } from 'react';
4
4
  import { BlockWrapper } from '../BlockWrapper';
5
5
  import { EditorBody } from '../EditorBody';
6
6
  import { BlockRenderer } from '../../../lib/blocks/BlockRenderer';
7
+ import { useEditor } from '../../../state/EditorContext';
7
8
  import { renderBlocksToEmail, generateNewsletterEmailHtml } from '../../../lib/email/EmailRenderer';
8
9
  import type { Block } from '../../../types/block';
9
10
  import type { NewsletterMetadata } from '../../../types/newsletter';
10
11
  import type { useSlashCommand } from '../hooks/useSlashCommand';
11
12
 
12
- // Add email-like styling for editor blocks
13
+ // Add email-like styling for editor blocks - Aligned with BotanicsAndYou brand styles
13
14
  const emailEditorStyles = `
15
+ .email-editor-content {
16
+ /* Newsletter Brand Variable Overrides */
17
+ --content-text-color: #1a2e26;
18
+ --content-font-family: 'Georgia', serif;
19
+ --content-font-size: 18px;
20
+ --content-line-height: 1.625;
21
+ --content-heading-font: 'Georgia', serif;
22
+ }
23
+
14
24
  .email-editor-content p,
15
25
  .email-editor-content .text-md {
16
- font-size: 15px !important;
17
- line-height: 1.8 !important;
18
- color: #1a2e26 !important;
19
- margin: 0 0 15px 0 !important;
20
- font-family: 'Georgia', serif !important;
26
+ font-size: var(--content-font-size);
27
+ line-height: var(--content-line-height);
28
+ color: var(--content-text-color);
29
+ margin: 0 0 1.5rem 0;
30
+ font-family: var(--content-font-family);
21
31
  }
22
32
 
23
33
  .email-editor-content h1,
@@ -26,52 +36,59 @@ const emailEditorStyles = `
26
36
  .email-editor-content h4,
27
37
  .email-editor-content h5,
28
38
  .email-editor-content h6 {
29
- font-family: 'Georgia', serif !important;
30
- font-weight: bold !important;
31
- color: #1a2e26 !important;
32
- margin: 20px 0 10px 0 !important;
33
- line-height: 1.3 !important;
39
+ font-family: 'Georgia', serif;
40
+ font-weight: bold;
41
+ color: #1a2e26;
42
+ margin: 2.5rem 0 1rem 0;
43
+ line-height: 1.2;
34
44
  }
35
45
 
36
46
  .email-editor-content h1 {
37
- font-size: 32px !important;
47
+ font-size: 48px;
48
+ letter-spacing: -0.02em;
38
49
  }
39
50
 
40
51
  .email-editor-content h2 {
41
- font-size: 24px !important;
52
+ font-size: 36px;
53
+ letter-spacing: -0.01em;
42
54
  }
43
55
 
44
56
  .email-editor-content h3 {
45
- font-size: 20px !important;
57
+ font-size: 30px;
58
+ }
59
+
60
+ .email-editor-content h4 {
61
+ font-size: 20px;
62
+ font-weight: 500;
46
63
  }
47
64
 
48
- .email-editor-content h4,
49
65
  .email-editor-content h5,
50
66
  .email-editor-content h6 {
51
- font-size: 18px !important;
67
+ font-size: 18px;
68
+ font-weight: 500;
52
69
  }
53
70
 
54
71
  .email-editor-content ul,
55
72
  .email-editor-content ol {
56
- margin: 15px 0 !important;
57
- padding-left: 25px !important;
58
- color: #1a2e26 !important;
59
- font-size: 15px !important;
60
- line-height: 1.8 !important;
73
+ margin: 1.5rem 0;
74
+ padding-left: 1.5rem;
75
+ color: #1a2e26;
76
+ font-size: 18px;
77
+ line-height: 1.625;
78
+ font-family: 'Georgia', serif;
61
79
  }
62
80
 
63
81
  .email-editor-content li {
64
- margin: 8px 0 !important;
65
- padding-left: 5px !important;
66
- line-height: 1.8 !important;
67
- color: #1a2e26 !important;
82
+ margin: 0.75rem 0;
83
+ padding-left: 0.25rem;
68
84
  }
69
85
 
70
86
  .email-editor-content img {
71
- max-width: 100% !important;
72
- height: auto !important;
73
- display: block !important;
74
- margin: 20px 0 !important;
87
+ max-width: 100%;
88
+ height: auto;
89
+ display: block;
90
+ margin: 2rem 0;
91
+ border-radius: 12px;
75
92
  }
76
93
 
77
94
  .email-block-wrapper {
@@ -79,15 +96,20 @@ const emailEditorStyles = `
79
96
  color: #1a2e26;
80
97
  }
81
98
 
82
- /* Ensure all blocks are visible */
99
+ /* Ensure all blocks are visible and match brand font */
100
+ .email-block-content {
101
+ font-family: 'Georgia', serif;
102
+ }
103
+
83
104
  .email-block-content > * {
84
- display: block !important;
85
- visibility: visible !important;
86
- opacity: 1 !important;
105
+ display: block;
106
+ visibility: visible;
107
+ opacity: 1;
87
108
  }
88
109
 
89
- /* Heading input styling to match preview */
90
- .email-block-content input[type="text"] {
110
+ /* Heading/Text input styling to match brand preview */
111
+ .email-block-content input[type="text"],
112
+ .email-block-content textarea {
91
113
  font-family: 'Georgia', serif !important;
92
114
  color: #1a2e26 !important;
93
115
  background: transparent !important;
@@ -238,12 +260,45 @@ export function EditorCanvas({
238
260
  onBlockMove,
239
261
  slashCommand,
240
262
  }: EditorCanvasProps) {
263
+ const { emailConfig: contextEmailConfig, unsubscribeTranslations: contextUnsubscribeTranslations } = useEditor();
264
+
265
+ // SMTP settings take priority over client config
266
+ const emailConfig = useMemo(() => {
267
+ let smtpLogoUrl = '';
268
+ let smtpUnsubscribeTranslations = {};
269
+
270
+ if (typeof window !== 'undefined') {
271
+ const pluginProps = (window as any).__JHITS_PLUGIN_PROPS__?.['plugin-newsletter'];
272
+ if (pluginProps?.emailConfig?.logoUrl) {
273
+ smtpLogoUrl = pluginProps.emailConfig.logoUrl;
274
+ }
275
+ if (pluginProps?.unsubscribeTranslations) {
276
+ smtpUnsubscribeTranslations = pluginProps.unsubscribeTranslations;
277
+ }
278
+ }
279
+
280
+ return {
281
+ ...contextEmailConfig,
282
+ logoUrl: smtpLogoUrl || contextEmailConfig?.logoUrl,
283
+ unsubscribeTranslations: {
284
+ en: 'Unsubscribe',
285
+ nl: 'Afmelden',
286
+ sv: 'Avanmälan',
287
+ ...contextUnsubscribeTranslations,
288
+ ...smtpUnsubscribeTranslations
289
+ }
290
+ };
291
+ }, [contextEmailConfig, contextUnsubscribeTranslations]);
292
+
241
293
  const titleRef = useRef<HTMLTextAreaElement>(null);
242
294
 
243
295
  // Inject email-like styles for editor
244
296
  useEffect(() => {
245
297
  const styleId = 'email-editor-styles';
246
- if (!document.getElementById(styleId)) {
298
+ const existingStyle = document.getElementById(styleId);
299
+ if (existingStyle) {
300
+ existingStyle.textContent = emailEditorStyles;
301
+ } else {
247
302
  const style = document.createElement('style');
248
303
  style.id = styleId;
249
304
  style.textContent = emailEditorStyles;
@@ -261,13 +316,13 @@ export function EditorCanvas({
261
316
 
262
317
  return (
263
318
  <div
264
- className="flex-1 overflow-y-auto overflow-x-hidden pb-40 custom-scrollbar selection:bg-primary/20 dark:selection:bg-primary/30 min-h-0"
319
+ className="flex-1 overflow-y-auto overflow-x-hidden pb-40 custom-scrollbar selection:bg-primary/20 dark:selection:bg-primary/30 min-h-0 h-full max-h-full"
265
320
  style={{
266
321
  backgroundColor: backgroundColors
267
322
  ? (darkMode && backgroundColors.dark
268
323
  ? backgroundColors.dark
269
324
  : backgroundColors.light)
270
- : '#faf9f6',
325
+ : '#ffffff',
271
326
  }}
272
327
  >
273
328
  {isPreviewMode ? (
@@ -348,45 +403,34 @@ export function EditorCanvas({
348
403
  backgroundColor: '#ffffff',
349
404
  }}>
350
405
  {/* Email Header with Logo - Centered like in email */}
351
- {(() => {
352
- // Get email config from window global (set by initNewsletterPlugin)
353
- let emailConfig: { logoUrl?: string; logoAlt?: string } = {};
354
- if (typeof window !== 'undefined' && (window as any).__JHITS_PLUGIN_PROPS__) {
355
- const pluginProps = (window as any).__JHITS_PLUGIN_PROPS__['plugin-newsletter'];
356
- if (pluginProps?.emailConfig) {
357
- emailConfig = pluginProps.emailConfig;
358
- }
359
- }
360
-
361
- return emailConfig.logoUrl ? (
362
- <div style={{
363
- padding: '40px 0 20px 0',
364
- textAlign: 'center',
365
- backgroundColor: '#ffffff',
366
- display: 'flex',
367
- justifyContent: 'center',
368
- alignItems: 'center',
369
- }}>
370
- <img
371
- src={emailConfig.logoUrl}
372
- alt={emailConfig.logoAlt || 'Logo'}
373
- style={{
374
- width: '180px',
375
- height: 'auto',
376
- display: 'block',
377
- margin: '0 auto',
378
- }}
379
- />
380
- </div>
381
- ) : null;
382
- })()}
406
+ {emailConfig?.logoUrl ? (
407
+ <div style={{
408
+ padding: '40px 0 20px 0',
409
+ textAlign: 'center',
410
+ backgroundColor: '#ffffff',
411
+ display: 'flex',
412
+ justifyContent: 'center',
413
+ alignItems: 'center',
414
+ }}>
415
+ <img
416
+ src={emailConfig.logoUrl}
417
+ alt={emailConfig.logoAlt || 'Logo'}
418
+ style={{
419
+ width: '180px',
420
+ height: 'auto',
421
+ display: 'block',
422
+ margin: '0 auto',
423
+ }}
424
+ />
425
+ </div>
426
+ ) : null}
383
427
 
384
428
  {/* Email Content Area */}
385
429
  <div className="email-editor-content" style={{
386
430
  fontFamily: "'Georgia', serif",
387
431
  color: '#1a2e26',
388
- lineHeight: '1.8',
389
- fontSize: '15px',
432
+ lineHeight: '1.625',
433
+ fontSize: '18px',
390
434
  padding: '0 50px 40px 50px',
391
435
  minHeight: '400px',
392
436
  backgroundColor: '#ffffff',
@@ -414,9 +458,11 @@ export function EditorCanvas({
414
458
  const slug = slugs[locale] || slugs.en;
415
459
  const unsubscribeUrl = `${baseUrl}${slug}?email=subscriber@example.com`;
416
460
 
417
- // Get unsubscribe text based on locale
418
- const isDutch = locale === 'nl';
419
- const unsubscribeText = isDutch ? 'Afmelden' : 'Unsubscribe';
461
+ // Get unsubscribe text (Priority: metadata manual override > global SMTP translation > hardcoded localized default)
462
+ const globalTranslations = (emailConfig as any).unsubscribeTranslations || {};
463
+ const hardcodedDefaults: Record<string, string> = { en: 'Unsubscribe', nl: 'Afmelden', sv: 'Avanmälan' };
464
+ const defaultText = globalTranslations[locale] || hardcodedDefaults[locale] || hardcodedDefaults.en;
465
+ const unsubscribeText = metadata?.unsubscribeText || defaultText;
420
466
 
421
467
  return (
422
468
  <>
@@ -452,31 +498,20 @@ export function EditorCanvas({
452
498
  </div>
453
499
 
454
500
  {/* Email Footer - Centered like in email */}
455
- {(() => {
456
- // Get email config from window global
457
- let emailConfig: { footerText?: string } = {};
458
- if (typeof window !== 'undefined' && (window as any).__JHITS_PLUGIN_PROPS__) {
459
- const pluginProps = (window as any).__JHITS_PLUGIN_PROPS__['plugin-newsletter'];
460
- if (pluginProps?.emailConfig) {
461
- emailConfig = pluginProps.emailConfig;
462
- }
463
- }
464
-
465
- return emailConfig.footerText ? (
466
- <div style={{
467
- padding: '40px 50px',
468
- textAlign: 'center',
469
- fontFamily: 'sans-serif',
470
- fontSize: '10px',
471
- color: '#a1a1aa',
472
- letterSpacing: '1px',
473
- borderTop: '1px solid #faf9f6',
474
- backgroundColor: '#ffffff',
475
- }}>
476
- {emailConfig.footerText}
477
- </div>
478
- ) : null;
479
- })()}
501
+ {emailConfig?.footerText ? (
502
+ <div style={{
503
+ padding: '40px 50px',
504
+ textAlign: 'center',
505
+ fontFamily: 'sans-serif',
506
+ fontSize: '10px',
507
+ color: '#a1a1aa',
508
+ letterSpacing: '1px',
509
+ borderTop: '1px solid #faf9f6',
510
+ backgroundColor: '#ffffff',
511
+ }}>
512
+ {emailConfig.footerText}
513
+ </div>
514
+ ) : null}
480
515
  </div>
481
516
  </div>
482
517
  </div>
@@ -492,19 +527,40 @@ export function EditorCanvas({
492
527
  * Matches the editor view structure for consistency
493
528
  */
494
529
  function EmailPreview({ title, blocks, siteId, locale, metadata }: { title: string; blocks: Block[]; siteId: string; locale: string; metadata?: NewsletterMetadata }) {
530
+ const { emailConfig: contextEmailConfig, unsubscribeTranslations: contextUnsubscribeTranslations } = useEditor();
531
+
532
+ // SMTP settings take priority over client config
533
+ const emailConfig = useMemo(() => {
534
+ let smtpLogoUrl = '';
535
+ let smtpUnsubscribeTranslations = {};
536
+
537
+ if (typeof window !== 'undefined') {
538
+ const pluginProps = (window as any).__JHITS_PLUGIN_PROPS__?.['plugin-newsletter'];
539
+ if (pluginProps?.emailConfig?.logoUrl) {
540
+ smtpLogoUrl = pluginProps.emailConfig.logoUrl;
541
+ }
542
+ if (pluginProps?.unsubscribeTranslations) {
543
+ smtpUnsubscribeTranslations = pluginProps.unsubscribeTranslations;
544
+ }
545
+ }
546
+
547
+ return {
548
+ ...contextEmailConfig,
549
+ logoUrl: smtpLogoUrl || contextEmailConfig?.logoUrl,
550
+ unsubscribeTranslations: {
551
+ en: 'Unsubscribe',
552
+ nl: 'Afmelden',
553
+ sv: 'Avanmälan',
554
+ ...contextUnsubscribeTranslations,
555
+ ...smtpUnsubscribeTranslations
556
+ }
557
+ };
558
+ }, [contextEmailConfig, contextUnsubscribeTranslations]);
559
+
495
560
  const [emailHtml, setEmailHtml] = useState<string>('');
496
561
  const iframeRef = useRef<HTMLIFrameElement>(null);
497
562
 
498
563
  useEffect(() => {
499
- // Get email config from window global (set by initNewsletterPlugin)
500
- let emailConfig: { logoUrl?: string; logoAlt?: string; footerText?: string } = {};
501
- if (typeof window !== 'undefined' && (window as any).__JHITS_PLUGIN_PROPS__) {
502
- const pluginProps = (window as any).__JHITS_PLUGIN_PROPS__['plugin-newsletter'];
503
- if (pluginProps?.emailConfig) {
504
- emailConfig = pluginProps.emailConfig;
505
- }
506
- }
507
-
508
564
  // Resolve image IDs to filenames before generating HTML
509
565
  const resolveImageBlocks = async () => {
510
566
  const resolvedBlocks = await Promise.all(blocks.map(async (block) => {
@@ -550,6 +606,12 @@ function EmailPreview({ title, blocks, siteId, locale, metadata }: { title: stri
550
606
  // Use placeholder email for preview
551
607
  const unsubscribeUrl = `${baseUrl}${slug}?email=subscriber@example.com`;
552
608
 
609
+ // Get unsubscribe text (Priority: metadata manual override > global SMTP translation > hardcoded localized default)
610
+ const globalTranslations = (emailConfig as any).unsubscribeTranslations || {};
611
+ const hardcodedDefaults: Record<string, string> = { en: 'Unsubscribe', nl: 'Afmelden', sv: 'Avanmälan' };
612
+ const defaultText = globalTranslations[locale] || hardcodedDefaults[locale] || hardcodedDefaults.en;
613
+ const unsubscribeText = metadata?.unsubscribeText || defaultText;
614
+
553
615
  // Generate email HTML with resolved image blocks
554
616
  const html = generateNewsletterEmailHtml(
555
617
  resolvedBlocks,
@@ -558,10 +620,11 @@ function EmailPreview({ title, blocks, siteId, locale, metadata }: { title: stri
558
620
  siteId,
559
621
  locale,
560
622
  baseUrl,
561
- logoUrl: emailConfig.logoUrl,
562
- logoAlt: emailConfig.logoAlt,
563
- footerText: emailConfig.footerText,
623
+ logoUrl: emailConfig?.logoUrl,
624
+ logoAlt: emailConfig?.logoAlt,
625
+ footerText: emailConfig?.footerText,
564
626
  unsubscribeUrl,
627
+ unsubscribeText,
565
628
  }
566
629
  );
567
630
  setEmailHtml(html);
@@ -579,7 +642,7 @@ function EmailPreview({ title, blocks, siteId, locale, metadata }: { title: stri
579
642
  };
580
643
 
581
644
  resolveImageBlocks();
582
- }, [title, blocks, siteId, locale, metadata]);
645
+ }, [title, blocks, siteId, locale, metadata, emailConfig]);
583
646
 
584
647
  if (blocks.length === 0) {
585
648
  return (
@@ -664,7 +727,7 @@ function EmailPreview({ title, blocks, siteId, locale, metadata }: { title: stri
664
727
  maxHeight: '900px',
665
728
  display: 'block',
666
729
  }}
667
- sandbox="allow-same-origin"
730
+ sandbox="allow-same-origin allow-scripts"
668
731
  />
669
732
  </div>
670
733
  </div>
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import React from 'react';
4
- import { Mail, Globe, Users } from 'lucide-react';
4
+ import { Mail, Globe, Users, Palette, Info } from 'lucide-react';
5
5
  import type { NewsletterMetadata } from '../../../types/newsletter';
6
6
 
7
7
  export interface EditorSidebarProps {
@@ -13,6 +13,8 @@ export interface EditorSidebarProps {
13
13
  languages?: string[];
14
14
  currentLanguage?: string;
15
15
  onLanguageChange?: (language: string) => void;
16
+ globalUnsubscribeText?: string;
17
+ onGlobalUnsubscribeUpdate?: (text: string) => void;
16
18
  }
17
19
 
18
20
  export function EditorSidebar({
@@ -24,6 +26,8 @@ export function EditorSidebar({
24
26
  languages = ['en'],
25
27
  currentLanguage = 'en',
26
28
  onLanguageChange,
29
+ globalUnsubscribeText = '',
30
+ onGlobalUnsubscribeUpdate,
27
31
  }: EditorSidebarProps) {
28
32
  return (
29
33
  <div className="p-8 w-80 min-w-0 max-w-full space-y-12 overflow-y-auto max-h-full">
@@ -32,7 +36,7 @@ export function EditorSidebar({
32
36
  <div className="flex items-center gap-3 mb-6">
33
37
  <Mail size={14} className="text-neutral-500 dark:text-neutral-400" />
34
38
  <label className="text-[10px] uppercase tracking-[0.2em] text-neutral-500 dark:text-neutral-400 font-black">
35
- Newsletter Settings
39
+ Campaign Settings
36
40
  </label>
37
41
  </div>
38
42
  <div className="space-y-4">
@@ -69,6 +73,57 @@ export function EditorSidebar({
69
73
  </div>
70
74
  </section>
71
75
 
76
+ {/* Global Branding Settings */}
77
+ <section className="pt-8 border-t border-neutral-200 dark:border-neutral-800">
78
+ <div className="flex items-center gap-3 mb-6">
79
+ <Palette size={14} className="text-neutral-500 dark:text-neutral-400" />
80
+ <label className="text-[10px] uppercase tracking-[0.2em] text-neutral-500 dark:text-neutral-400 font-black">
81
+ Global Branding
82
+ </label>
83
+ </div>
84
+ <div className="space-y-4">
85
+ <div className="bg-blue-50/50 dark:bg-blue-900/10 border border-blue-100 dark:border-blue-900/30 rounded-xl p-3 mb-4">
86
+ <div className="flex gap-2 text-blue-600 dark:text-blue-400">
87
+ <Info size={14} className="flex-shrink-0 mt-0.5" />
88
+ <p className="text-[9px] leading-relaxed font-medium">
89
+ Settings below apply to <span className="font-bold">ALL</span> newsletters sent in <span className="uppercase font-bold">{currentLanguage}</span>.
90
+ </p>
91
+ </div>
92
+ </div>
93
+ <div>
94
+ <label className="text-[10px] text-neutral-500 dark:text-neutral-400 uppercase font-bold block mb-2">
95
+ Default Unsubscribe Text
96
+ </label>
97
+ <input
98
+ type="text"
99
+ value={globalUnsubscribeText}
100
+ onChange={(e) => onGlobalUnsubscribeUpdate?.(e.target.value)}
101
+ placeholder="e.g. Unsubscribe from this list"
102
+ className="w-full px-3 py-2 text-xs bg-dashboard-card border border-dashboard-border rounded-lg outline-none focus:border-primary transition-all text-dashboard-text italic"
103
+ />
104
+ <p className="text-[9px] text-neutral-400 dark:text-neutral-500 mt-2 leading-relaxed">
105
+ This updates your primary configuration for this language.
106
+ </p>
107
+ </div>
108
+
109
+ <div>
110
+ <label className="text-[10px] text-neutral-500 dark:text-neutral-400 uppercase font-bold block mb-2">
111
+ Local Campaign Override
112
+ </label>
113
+ <input
114
+ type="text"
115
+ value={metadata.unsubscribeText || ''}
116
+ onChange={(e) => onMetadataUpdate({ unsubscribeText: e.target.value })}
117
+ placeholder="Optional override for this email only"
118
+ className="w-full px-3 py-2 text-xs bg-dashboard-bg border border-dashboard-border rounded-lg outline-none focus:border-primary transition-all text-dashboard-text"
119
+ />
120
+ <p className="text-[9px] text-neutral-400 dark:text-neutral-500 mt-1">
121
+ Only use this if you need a specific text for this single campaign.
122
+ </p>
123
+ </div>
124
+ </div>
125
+ </section>
126
+
72
127
  {/* Recipient Filter */}
73
128
  <section className="pt-8 border-t border-neutral-200 dark:border-neutral-800">
74
129
  <div className="flex items-center gap-3 mb-6">
@@ -1,54 +1,15 @@
1
1
  import { useState, useEffect } from 'react';
2
2
  import { blockRegistry } from '../../../registry/BlockRegistry';
3
+ import { useEditor } from '../../../state/EditorContext';
3
4
 
4
5
  export function useRegisteredBlocks() {
5
- const [registeredBlocks, setRegisteredBlocks] = useState(() => {
6
- const initial = blockRegistry.getAll();
7
- return initial;
8
- });
6
+ const { registeredBlockTypes } = useEditor();
7
+ const [registeredBlocks, setRegisteredBlocks] = useState(() => blockRegistry.getAll());
9
8
 
10
- // Watch for registry changes and update state
9
+ // Update local state whenever registeredBlockTypes changes in context
11
10
  useEffect(() => {
12
- // Check immediately
13
- const checkBlocks = () => {
14
- const currentBlocks = blockRegistry.getAll();
15
- const hasChanged = currentBlocks.length !== registeredBlocks.length ||
16
- currentBlocks.some((b, i) => b.type !== registeredBlocks[i]?.type) ||
17
- registeredBlocks.some((b, i) => b.type !== currentBlocks[i]?.type);
18
-
19
- if (hasChanged) {
20
- setRegisteredBlocks([...currentBlocks]);
21
- }
22
- };
23
-
24
- // Initial check
25
- checkBlocks();
26
-
27
- // Poll for registry changes (blocks are registered asynchronously in useEffect)
28
- let pollCount = 0;
29
- const interval = setInterval(() => {
30
- pollCount++;
31
- checkBlocks();
32
- // Stop polling after 5 seconds (25 checks at 200ms)
33
- if (pollCount > 25) {
34
- clearInterval(interval);
35
- }
36
- }, 200);
37
-
38
- // Also check after delays to catch initial registrations
39
- const timeouts = [
40
- setTimeout(checkBlocks, 50),
41
- setTimeout(checkBlocks, 100),
42
- setTimeout(checkBlocks, 300),
43
- setTimeout(checkBlocks, 500),
44
- setTimeout(checkBlocks, 1000),
45
- ];
46
-
47
- return () => {
48
- clearInterval(interval);
49
- timeouts.forEach(clearTimeout);
50
- };
51
- }, [registeredBlocks.length]);
11
+ setRegisteredBlocks(blockRegistry.getAll());
12
+ }, [registeredBlockTypes]);
52
13
 
53
14
  return registeredBlocks;
54
15
  }