@jhits/plugin-newsletter 0.0.6 → 0.0.8

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 (45) hide show
  1. package/package.json +8 -9
  2. package/src/api/handler.ts +0 -693
  3. package/src/api/router.ts +0 -111
  4. package/src/index.server.ts +0 -12
  5. package/src/index.tsx +0 -313
  6. package/src/index.tsx.patch +0 -98
  7. package/src/init.tsx +0 -72
  8. package/src/lib/blocks/BlockRenderer.tsx +0 -125
  9. package/src/lib/email/EmailRenderer.tsx +0 -425
  10. package/src/lib/email/index.ts +0 -6
  11. package/src/lib/mappers/apiMapper.ts +0 -57
  12. package/src/lib/utils/blockHelpers.ts +0 -71
  13. package/src/lib/utils/slugify.ts +0 -43
  14. package/src/registry/BlockRegistry.ts +0 -53
  15. package/src/registry/index.ts +0 -5
  16. package/src/state/EditorContext.tsx +0 -279
  17. package/src/state/index.ts +0 -10
  18. package/src/state/reducer.ts +0 -561
  19. package/src/state/types.ts +0 -154
  20. package/src/types/block.ts +0 -275
  21. package/src/types/newsletter.ts +0 -151
  22. package/src/types/registry.ts +0 -14
  23. package/src/views/CanvasEditor/BlockWrapper.tsx +0 -143
  24. package/src/views/CanvasEditor/CanvasEditorView.tsx +0 -249
  25. package/src/views/CanvasEditor/EditorBody.tsx +0 -95
  26. package/src/views/CanvasEditor/EditorHeader.tsx +0 -139
  27. package/src/views/CanvasEditor/components/CustomBlockItem.tsx +0 -83
  28. package/src/views/CanvasEditor/components/EditorCanvas.tsx +0 -674
  29. package/src/views/CanvasEditor/components/EditorLibrary.tsx +0 -120
  30. package/src/views/CanvasEditor/components/EditorSidebar.tsx +0 -156
  31. package/src/views/CanvasEditor/components/ErrorBanner.tsx +0 -31
  32. package/src/views/CanvasEditor/components/LibraryItem.tsx +0 -71
  33. package/src/views/CanvasEditor/components/SlashCommandDetector.tsx +0 -196
  34. package/src/views/CanvasEditor/components/SlashCommandMenu.tsx +0 -131
  35. package/src/views/CanvasEditor/components/index.ts +0 -16
  36. package/src/views/CanvasEditor/hooks/index.ts +0 -7
  37. package/src/views/CanvasEditor/hooks/useKeyboardShortcuts.ts +0 -136
  38. package/src/views/CanvasEditor/hooks/useNewsletterLoader.ts +0 -34
  39. package/src/views/CanvasEditor/hooks/useRegisteredBlocks.ts +0 -54
  40. package/src/views/CanvasEditor/hooks/useSlashCommand.ts +0 -106
  41. package/src/views/CanvasEditor/index.ts +0 -12
  42. package/src/views/NewsletterEditor.tsx +0 -38
  43. package/src/views/NewsletterManager.tsx +0 -240
  44. package/src/views/SettingsView.tsx +0 -216
  45. package/src/views/SubscribersView.tsx +0 -269
@@ -1,674 +0,0 @@
1
- 'use client';
2
-
3
- import React, { useRef, useEffect, useState } from 'react';
4
- import { BlockWrapper } from '../BlockWrapper';
5
- import { EditorBody } from '../EditorBody';
6
- import { BlockRenderer } from '../../../lib/blocks/BlockRenderer';
7
- import { renderBlocksToEmail, generateNewsletterEmailHtml } from '../../../lib/email/EmailRenderer';
8
- import type { Block } from '../../../types/block';
9
- import type { NewsletterMetadata } from '../../../types/newsletter';
10
- import type { useSlashCommand } from '../hooks/useSlashCommand';
11
-
12
- // Add email-like styling for editor blocks
13
- const emailEditorStyles = `
14
- .email-editor-content p,
15
- .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;
21
- }
22
-
23
- .email-editor-content h1,
24
- .email-editor-content h2,
25
- .email-editor-content h3,
26
- .email-editor-content h4,
27
- .email-editor-content h5,
28
- .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;
34
- }
35
-
36
- .email-editor-content h1 {
37
- font-size: 32px !important;
38
- }
39
-
40
- .email-editor-content h2 {
41
- font-size: 24px !important;
42
- }
43
-
44
- .email-editor-content h3 {
45
- font-size: 20px !important;
46
- }
47
-
48
- .email-editor-content h4,
49
- .email-editor-content h5,
50
- .email-editor-content h6 {
51
- font-size: 18px !important;
52
- }
53
-
54
- .email-editor-content ul,
55
- .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;
61
- }
62
-
63
- .email-editor-content li {
64
- margin: 8px 0 !important;
65
- padding-left: 5px !important;
66
- line-height: 1.8 !important;
67
- color: #1a2e26 !important;
68
- }
69
-
70
- .email-editor-content img {
71
- max-width: 100% !important;
72
- height: auto !important;
73
- display: block !important;
74
- margin: 20px 0 !important;
75
- }
76
-
77
- .email-block-wrapper {
78
- font-family: 'Georgia', serif;
79
- color: #1a2e26;
80
- }
81
-
82
- /* Ensure all blocks are visible */
83
- .email-block-content > * {
84
- display: block !important;
85
- visibility: visible !important;
86
- opacity: 1 !important;
87
- }
88
-
89
- /* Heading input styling to match preview */
90
- .email-block-content input[type="text"] {
91
- font-family: 'Georgia', serif !important;
92
- color: #1a2e26 !important;
93
- background: transparent !important;
94
- border: none !important;
95
- outline: none !important;
96
- width: 100% !important;
97
- padding: 0 !important;
98
- }
99
-
100
- .email-block-content input[type="text"]::placeholder {
101
- color: #a1a1aa !important;
102
- }
103
-
104
- /* Table block styling */
105
- .email-block-content table {
106
- width: 100% !important;
107
- border-collapse: collapse !important;
108
- margin: 20px 0 !important;
109
- display: table !important;
110
- visibility: visible !important;
111
- opacity: 1 !important;
112
- }
113
-
114
- .email-block-content table th,
115
- .email-block-content table td {
116
- padding: 12px !important;
117
- border: 1px solid #e5e7eb !important;
118
- text-align: left !important;
119
- color: #1a2e26 !important;
120
- font-size: 15px !important;
121
- line-height: 1.8 !important;
122
- background-color: transparent !important;
123
- }
124
-
125
- .email-block-content table th {
126
- background-color: #f9fafb !important;
127
- font-weight: bold !important;
128
- }
129
-
130
- /* Table container styling */
131
- .email-block-content div[class*="overflow-hidden"],
132
- .email-block-content div[class*="rounded"] {
133
- display: block !important;
134
- visibility: visible !important;
135
- opacity: 1 !important;
136
- }
137
-
138
- /* List block styling */
139
- .email-block-content ul,
140
- .email-block-content ol {
141
- display: block !important;
142
- margin: 15px 0 !important;
143
- padding-left: 25px !important;
144
- visibility: visible !important;
145
- opacity: 1 !important;
146
- }
147
-
148
- .email-block-content li {
149
- display: list-item !important;
150
- margin: 8px 0 !important;
151
- visibility: visible !important;
152
- opacity: 1 !important;
153
- }
154
-
155
- /* List container styling */
156
- .email-block-content div[class*="bg-white"][class*="rounded"] {
157
- display: block !important;
158
- visibility: visible !important;
159
- opacity: 1 !important;
160
- background-color: #ffffff !important;
161
- border: 1px solid #e5e7eb !important;
162
- }
163
-
164
- /* Recipe block styling */
165
- .email-block-content div[class*="recipe"],
166
- .email-block-content div[class*="Recipe"] {
167
- display: block !important;
168
- visibility: visible !important;
169
- opacity: 1 !important;
170
- }
171
-
172
- /* Ensure all container divs are visible */
173
- .email-block-content > div {
174
- display: block !important;
175
- visibility: visible !important;
176
- opacity: 1 !important;
177
- }
178
-
179
- .email-block-content div[class*="bg-white"],
180
- .email-block-content div[class*="border"] {
181
- display: block !important;
182
- visibility: visible !important;
183
- opacity: 1 !important;
184
- }
185
-
186
- /* Ensure buttons and inputs are visible */
187
- .email-block-content button,
188
- .email-block-content input {
189
- visibility: visible !important;
190
- opacity: 1 !important;
191
- }
192
-
193
- /* Override any hidden or invisible states */
194
- .email-block-content [style*="display: none"],
195
- .email-block-content [style*="visibility: hidden"],
196
- .email-block-content [style*="opacity: 0"] {
197
- display: block !important;
198
- visibility: visible !important;
199
- opacity: 1 !important;
200
- }
201
- `;
202
-
203
- export interface EditorCanvasProps {
204
- isPreviewMode: boolean;
205
- contentBlocks: Block[];
206
- title: string;
207
- siteId: string;
208
- locale: string;
209
- darkMode: boolean;
210
- backgroundColors?: {
211
- light: string;
212
- dark?: string;
213
- };
214
- metadata?: NewsletterMetadata;
215
- onTitleChange: (title: string) => void;
216
- onMetadataChange?: (metadata: Partial<NewsletterMetadata>) => void;
217
- onBlockAdd: (type: string, index: number, containerId?: string) => void;
218
- onBlockUpdate: (id: string, data: Partial<Block['data']>) => void;
219
- onBlockDelete: (id: string) => void;
220
- onBlockMove: (id: string, newIndex: number, containerId?: string) => void;
221
- slashCommand?: ReturnType<typeof useSlashCommand>;
222
- }
223
-
224
- export function EditorCanvas({
225
- isPreviewMode,
226
- contentBlocks,
227
- title,
228
- siteId,
229
- locale,
230
- darkMode,
231
- backgroundColors,
232
- metadata,
233
- onTitleChange,
234
- onMetadataChange,
235
- onBlockAdd,
236
- onBlockUpdate,
237
- onBlockDelete,
238
- onBlockMove,
239
- slashCommand,
240
- }: EditorCanvasProps) {
241
- const titleRef = useRef<HTMLTextAreaElement>(null);
242
-
243
- // Inject email-like styles for editor
244
- useEffect(() => {
245
- const styleId = 'email-editor-styles';
246
- if (!document.getElementById(styleId)) {
247
- const style = document.createElement('style');
248
- style.id = styleId;
249
- style.textContent = emailEditorStyles;
250
- document.head.appendChild(style);
251
- }
252
- }, []);
253
-
254
- // Handle Title Auto-resize
255
- useEffect(() => {
256
- if (titleRef.current) {
257
- titleRef.current.style.height = 'auto';
258
- titleRef.current.style.height = `${titleRef.current.scrollHeight}px`;
259
- }
260
- }, [title]);
261
-
262
- return (
263
- <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"
265
- style={{
266
- backgroundColor: backgroundColors
267
- ? (darkMode && backgroundColors.dark
268
- ? backgroundColors.dark
269
- : backgroundColors.light)
270
- : '#faf9f6',
271
- }}
272
- >
273
- {isPreviewMode ? (
274
- <div className="mx-auto transition-all duration-500 max-w-[800px] px-6 py-6 w-full">
275
- <EmailPreview
276
- title={title}
277
- blocks={contentBlocks}
278
- siteId={siteId}
279
- locale={locale}
280
- metadata={metadata}
281
- />
282
- </div>
283
- ) : (
284
- <div className="mx-auto transition-all duration-500 max-w-[800px] px-6 py-6 w-full">
285
- {/* Email Client Window - Gmail/Outlook Style */}
286
- <div className="bg-gradient-to-b from-neutral-50 to-neutral-100 dark:from-neutral-900 dark:to-neutral-800 rounded-2xl p-6 border border-neutral-200 dark:border-neutral-700 shadow-xl">
287
- {/* Email Client Window */}
288
- <div className="bg-white dark:bg-neutral-800 rounded-lg shadow-2xl overflow-hidden">
289
- {/* Email Client Header - Gmail Style */}
290
- <div className="bg-white dark:bg-neutral-800 border-b border-neutral-200 dark:border-neutral-700">
291
- {/* Toolbar */}
292
- <div className="px-4 py-2 flex items-center justify-between border-b border-neutral-100 dark:border-neutral-700">
293
- <div className="flex items-center gap-2">
294
- <button className="p-1.5 hover:bg-neutral-100 dark:hover:bg-neutral-700 rounded" title="Close">
295
- <svg className="w-4 h-4 text-neutral-600 dark:text-neutral-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
296
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
297
- </svg>
298
- </button>
299
- <button className="p-1.5 hover:bg-neutral-100 dark:hover:bg-neutral-700 rounded" title="Minimize">
300
- <svg className="w-4 h-4 text-neutral-600 dark:text-neutral-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
301
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M20 12H4" />
302
- </svg>
303
- </button>
304
- <button className="p-1.5 hover:bg-neutral-100 dark:hover:bg-neutral-700 rounded" title="Fullscreen">
305
- <svg className="w-4 h-4 text-neutral-600 dark:text-neutral-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
306
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4" />
307
- </svg>
308
- </button>
309
- </div>
310
- <div className="flex items-center gap-2">
311
- <span className="text-xs text-neutral-500 dark:text-neutral-400 font-medium">Compose Email</span>
312
- </div>
313
- </div>
314
-
315
- {/* Email Headers */}
316
- <div className="px-4 py-3 border-b border-neutral-100 dark:border-neutral-700">
317
- <div className="space-y-3">
318
- <div className="flex items-center gap-2">
319
- <label className="text-xs text-neutral-500 dark:text-neutral-400 font-medium w-16 flex-shrink-0">To:</label>
320
- <input
321
- type="text"
322
- value="All Subscribers"
323
- readOnly
324
- className="flex-1 text-sm bg-transparent border-none outline-none text-neutral-700 dark:text-neutral-300 placeholder:text-neutral-400"
325
- />
326
- </div>
327
- <div className="flex items-center gap-2">
328
- <label className="text-xs text-neutral-500 dark:text-neutral-400 font-medium w-16 flex-shrink-0">Subject:</label>
329
- <input
330
- type="text"
331
- value={metadata?.subject || ''}
332
- onChange={(e) => onMetadataChange?.({
333
- subject: e.target.value,
334
- previewText: metadata?.previewText,
335
- lang: metadata?.lang,
336
- recipientFilter: metadata?.recipientFilter,
337
- })}
338
- placeholder="Enter email subject..."
339
- className="flex-1 text-sm bg-transparent border-none outline-none text-neutral-900 dark:text-neutral-100 placeholder:text-neutral-400 font-medium"
340
- />
341
- </div>
342
- </div>
343
- </div>
344
- </div>
345
-
346
- {/* Email Content Container - Matches email HTML structure */}
347
- <div className="bg-white dark:bg-neutral-800" style={{
348
- backgroundColor: '#ffffff',
349
- }}>
350
- {/* 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
- })()}
383
-
384
- {/* Email Content Area */}
385
- <div className="email-editor-content" style={{
386
- fontFamily: "'Georgia', serif",
387
- color: '#1a2e26',
388
- lineHeight: '1.8',
389
- fontSize: '15px',
390
- padding: '0 50px 40px 50px',
391
- minHeight: '400px',
392
- backgroundColor: '#ffffff',
393
- }}>
394
- <EditorBody
395
- blocks={contentBlocks}
396
- darkMode={darkMode}
397
- backgroundColors={backgroundColors}
398
- onBlockAdd={onBlockAdd}
399
- onBlockUpdate={onBlockUpdate}
400
- onBlockDelete={onBlockDelete}
401
- onBlockMove={onBlockMove}
402
- slashCommand={slashCommand}
403
- />
404
-
405
- {/* Unsubscribe Link - Matching email preview */}
406
- {(() => {
407
- // Generate unsubscribe URL (similar to welcome email)
408
- const baseUrl = typeof window !== 'undefined' ? window.location.origin : '';
409
- const slugs: Record<string, string> = {
410
- sv: '/avmälla',
411
- nl: '/afmelden',
412
- en: '/unsubscribe',
413
- };
414
- const slug = slugs[locale] || slugs.en;
415
- const unsubscribeUrl = `${baseUrl}${slug}?email=subscriber@example.com`;
416
-
417
- // Get unsubscribe text based on locale
418
- const isDutch = locale === 'nl';
419
- const unsubscribeText = isDutch ? 'Afmelden' : 'Unsubscribe';
420
-
421
- return (
422
- <>
423
- {/* Divider */}
424
- <div style={{
425
- height: '1px',
426
- width: '40px',
427
- backgroundColor: '#1a2e2620',
428
- margin: '30px auto',
429
- }}></div>
430
-
431
- {/* Unsubscribe Link */}
432
- <p style={{
433
- textAlign: 'center',
434
- fontSize: '12px',
435
- color: '#a1a1aa',
436
- margin: 0,
437
- }}>
438
- <a
439
- href={unsubscribeUrl}
440
- style={{
441
- color: '#a1a1aa',
442
- textDecoration: 'none',
443
- }}
444
- onClick={(e) => e.preventDefault()}
445
- >
446
- {unsubscribeText}
447
- </a>
448
- </p>
449
- </>
450
- );
451
- })()}
452
- </div>
453
-
454
- {/* 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
- })()}
480
- </div>
481
- </div>
482
- </div>
483
- </div>
484
- )}
485
- </div>
486
- );
487
- }
488
-
489
- /**
490
- * Email Preview Component
491
- * Shows how the newsletter will look in email clients
492
- * Matches the editor view structure for consistency
493
- */
494
- function EmailPreview({ title, blocks, siteId, locale, metadata }: { title: string; blocks: Block[]; siteId: string; locale: string; metadata?: NewsletterMetadata }) {
495
- const [emailHtml, setEmailHtml] = useState<string>('');
496
- const iframeRef = useRef<HTMLIFrameElement>(null);
497
-
498
- 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
- // Resolve image IDs to filenames before generating HTML
509
- const resolveImageBlocks = async () => {
510
- const resolvedBlocks = await Promise.all(blocks.map(async (block) => {
511
- if (block.type === 'image' && block.data.imageId) {
512
- const imageId = block.data.imageId as string;
513
- // Check if already a filename
514
- const hasFileExtension = /\.(jpg|jpeg|png|webp|gif|svg)$/i.test(imageId);
515
- const looksLikeTimestamp = /^\d+-/.test(imageId);
516
-
517
- if (hasFileExtension || looksLikeTimestamp) {
518
- // Already a filename, use it directly
519
- return block;
520
- }
521
-
522
- // Need to resolve semantic ID to filename
523
- try {
524
- const response = await fetch(`/api/plugin-images/resolve?id=${encodeURIComponent(imageId)}`);
525
- if (response.ok) {
526
- const data = await response.json();
527
- return {
528
- ...block,
529
- data: {
530
- ...block.data,
531
- imageId: data.filename || imageId, // Use resolved filename
532
- },
533
- };
534
- }
535
- } catch (error) {
536
- console.warn(`Failed to resolve image ID ${imageId}:`, error);
537
- }
538
- }
539
- return block;
540
- }));
541
-
542
- // Generate unsubscribe URL (similar to welcome email)
543
- const baseUrl = typeof window !== 'undefined' ? window.location.origin : '';
544
- const slugs: Record<string, string> = {
545
- sv: '/avmälla',
546
- nl: '/afmelden',
547
- en: '/unsubscribe',
548
- };
549
- const slug = slugs[locale] || slugs.en;
550
- // Use placeholder email for preview
551
- const unsubscribeUrl = `${baseUrl}${slug}?email=subscriber@example.com`;
552
-
553
- // Generate email HTML with resolved image blocks
554
- const html = generateNewsletterEmailHtml(
555
- resolvedBlocks,
556
- { subject: metadata?.subject || '', previewText: metadata?.previewText || '' },
557
- {
558
- siteId,
559
- locale,
560
- baseUrl,
561
- logoUrl: emailConfig.logoUrl,
562
- logoAlt: emailConfig.logoAlt,
563
- footerText: emailConfig.footerText,
564
- unsubscribeUrl,
565
- }
566
- );
567
- setEmailHtml(html);
568
-
569
- // Update iframe content
570
- if (iframeRef.current) {
571
- const iframe = iframeRef.current;
572
- const iframeDoc = iframe.contentDocument || iframe.contentWindow?.document;
573
- if (iframeDoc) {
574
- iframeDoc.open();
575
- iframeDoc.write(html);
576
- iframeDoc.close();
577
- }
578
- }
579
- };
580
-
581
- resolveImageBlocks();
582
- }, [title, blocks, siteId, locale, metadata]);
583
-
584
- if (blocks.length === 0) {
585
- return (
586
- <div className="bg-gradient-to-b from-neutral-50 to-neutral-100 dark:from-neutral-900 dark:to-neutral-800 rounded-2xl p-6 border border-neutral-200 dark:border-neutral-700 shadow-xl">
587
- <div className="bg-white dark:bg-neutral-800 rounded-lg shadow-2xl overflow-hidden">
588
- <div className="text-center py-20 text-neutral-400 dark:text-neutral-500">
589
- <p className="text-sm">No content blocks yet. Switch to Edit mode to add blocks.</p>
590
- <p className="text-xs mt-2 text-neutral-500 dark:text-neutral-600">
591
- Email preview will appear here once you add blocks.
592
- </p>
593
- </div>
594
- </div>
595
- </div>
596
- );
597
- }
598
-
599
- return (
600
- <div>
601
- {/* Email Client Window - Gmail/Outlook Style - Same as Editor */}
602
- <div className="bg-gradient-to-b from-neutral-50 to-neutral-100 dark:from-neutral-900 dark:to-neutral-800 rounded-2xl p-6 border border-neutral-200 dark:border-neutral-700 shadow-xl">
603
- {/* Email Client Window */}
604
- <div className="bg-white dark:bg-neutral-800 rounded-lg shadow-2xl overflow-hidden">
605
- {/* Email Client Header - Gmail Style */}
606
- <div className="bg-white dark:bg-neutral-800 border-b border-neutral-200 dark:border-neutral-700">
607
- {/* Toolbar */}
608
- <div className="px-4 py-2 flex items-center justify-between border-b border-neutral-100 dark:border-neutral-700">
609
- <div className="flex items-center gap-2">
610
- <button className="p-1.5 hover:bg-neutral-100 dark:hover:bg-neutral-700 rounded" title="Close">
611
- <svg className="w-4 h-4 text-neutral-600 dark:text-neutral-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
612
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
613
- </svg>
614
- </button>
615
- <button className="p-1.5 hover:bg-neutral-100 dark:hover:bg-neutral-700 rounded" title="Minimize">
616
- <svg className="w-4 h-4 text-neutral-600 dark:text-neutral-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
617
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M20 12H4" />
618
- </svg>
619
- </button>
620
- <button className="p-1.5 hover:bg-neutral-100 dark:hover:bg-neutral-700 rounded" title="Fullscreen">
621
- <svg className="w-4 h-4 text-neutral-600 dark:text-neutral-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
622
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4" />
623
- </svg>
624
- </button>
625
- </div>
626
- <div className="flex items-center gap-2">
627
- <span className="text-xs text-neutral-500 dark:text-neutral-400 font-medium">Email Preview</span>
628
- </div>
629
- </div>
630
-
631
- {/* Email Headers */}
632
- <div className="px-4 py-3 border-b border-neutral-100 dark:border-neutral-700">
633
- <div className="space-y-3">
634
- <div className="flex items-center gap-2">
635
- <label className="text-xs text-neutral-500 dark:text-neutral-400 font-medium w-16 flex-shrink-0">To:</label>
636
- <input
637
- type="text"
638
- value="All Subscribers"
639
- readOnly
640
- className="flex-1 text-sm bg-transparent border-none outline-none text-neutral-700 dark:text-neutral-300 placeholder:text-neutral-400"
641
- />
642
- </div>
643
- <div className="flex items-center gap-2">
644
- <label className="text-xs text-neutral-500 dark:text-neutral-400 font-medium w-16 flex-shrink-0">Subject:</label>
645
- <input
646
- type="text"
647
- value={metadata?.subject || 'Newsletter Subject'}
648
- readOnly
649
- className="flex-1 text-sm bg-transparent border-none outline-none text-neutral-900 dark:text-neutral-100 placeholder:text-neutral-400 font-medium"
650
- />
651
- </div>
652
- </div>
653
- </div>
654
- </div>
655
-
656
- {/* Email Content - iframe showing actual email */}
657
- <div className="bg-white dark:bg-neutral-800">
658
- <iframe
659
- ref={iframeRef}
660
- title="Email Preview"
661
- className="w-full border-0 bg-white"
662
- style={{
663
- minHeight: '500px',
664
- maxHeight: '900px',
665
- display: 'block',
666
- }}
667
- sandbox="allow-same-origin"
668
- />
669
- </div>
670
- </div>
671
- </div>
672
- </div>
673
- );
674
- }