@jhits/plugin-newsletter 0.0.15 → 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 (90) 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/newsletters.d.ts.map +1 -1
  4. package/dist/api/handlers/newsletters.js +33 -16
  5. package/dist/api/handlers/send-newsletter.d.ts.map +1 -1
  6. package/dist/api/handlers/send-newsletter.js +54 -6
  7. package/dist/api/handlers/settings.d.ts.map +1 -1
  8. package/dist/api/handlers/settings.js +51 -1
  9. package/dist/index.d.ts +27 -10
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +15 -122
  12. package/dist/lib/blocks/BlockRenderer.d.ts.map +1 -1
  13. package/dist/lib/blocks/BlockRenderer.js +14 -2
  14. package/dist/lib/email/EmailRenderer.d.ts +1 -0
  15. package/dist/lib/email/EmailRenderer.d.ts.map +1 -1
  16. package/dist/lib/email/EmailRenderer.js +31 -19
  17. package/dist/lib/utils/config-resolver.d.ts +33 -0
  18. package/dist/lib/utils/config-resolver.d.ts.map +1 -0
  19. package/dist/lib/utils/config-resolver.js +47 -0
  20. package/dist/registry/BlockRegistry.d.ts +9 -1
  21. package/dist/registry/BlockRegistry.d.ts.map +1 -1
  22. package/dist/registry/BlockRegistry.js +126 -8
  23. package/dist/state/EditorContext.d.ts +11 -1
  24. package/dist/state/EditorContext.d.ts.map +1 -1
  25. package/dist/state/EditorContext.js +23 -5
  26. package/dist/state/types.d.ts +12 -0
  27. package/dist/state/types.d.ts.map +1 -1
  28. package/dist/types/block.d.ts +9 -0
  29. package/dist/types/block.d.ts.map +1 -1
  30. package/dist/types/newsletter.d.ts +4 -0
  31. package/dist/types/newsletter.d.ts.map +1 -1
  32. package/dist/views/CanvasEditor/BlockWrapper.d.ts.map +1 -1
  33. package/dist/views/CanvasEditor/BlockWrapper.js +24 -3
  34. package/dist/views/CanvasEditor/CanvasEditorView.d.ts.map +1 -1
  35. package/dist/views/CanvasEditor/CanvasEditorView.js +77 -17
  36. package/dist/views/CanvasEditor/EditorBody.d.ts.map +1 -1
  37. package/dist/views/CanvasEditor/EditorBody.js +1 -1
  38. package/dist/views/CanvasEditor/components/EditorCanvas.d.ts.map +1 -1
  39. package/dist/views/CanvasEditor/components/EditorCanvas.js +158 -100
  40. package/dist/views/CanvasEditor/components/EditorSidebar.d.ts +3 -1
  41. package/dist/views/CanvasEditor/components/EditorSidebar.d.ts.map +1 -1
  42. package/dist/views/CanvasEditor/components/EditorSidebar.js +3 -3
  43. package/dist/views/CanvasEditor/hooks/useRegisteredBlocks.d.ts +1 -1
  44. package/dist/views/CanvasEditor/hooks/useRegisteredBlocks.d.ts.map +1 -1
  45. package/dist/views/CanvasEditor/hooks/useRegisteredBlocks.js +6 -40
  46. package/dist/views/NewsletterManager.d.ts.map +1 -1
  47. package/dist/views/NewsletterManager.js +87 -5
  48. package/dist/views/components/DomainPromptModal.d.ts +13 -0
  49. package/dist/views/components/DomainPromptModal.d.ts.map +1 -0
  50. package/dist/views/components/DomainPromptModal.js +58 -0
  51. package/dist/views/components/NewsletterCard.d.ts +16 -0
  52. package/dist/views/components/NewsletterCard.d.ts.map +1 -0
  53. package/dist/views/components/NewsletterCard.js +94 -0
  54. package/dist/views/components/NewsletterGrid.d.ts +16 -0
  55. package/dist/views/components/NewsletterGrid.d.ts.map +1 -0
  56. package/dist/views/components/NewsletterGrid.js +13 -0
  57. package/dist/views/components/SendNewsletterModal.d.ts.map +1 -1
  58. package/dist/views/components/SendNewsletterModal.js +91 -22
  59. package/dist/views/components/SmtpSettingsModal.d.ts.map +1 -1
  60. package/dist/views/components/SmtpSettingsModal.js +10 -0
  61. package/dist/views/components/TestEmailModal.d.ts.map +1 -1
  62. package/dist/views/components/TestEmailModal.js +86 -17
  63. package/package.json +53 -9
  64. package/src/api/email-utils.ts +53 -4
  65. package/src/api/handlers/newsletters.ts +40 -20
  66. package/src/api/handlers/send-newsletter.ts +65 -6
  67. package/src/api/handlers/settings.ts +60 -2
  68. package/src/index.tsx +49 -155
  69. package/src/lib/blocks/BlockRenderer.tsx +16 -2
  70. package/src/lib/email/EmailRenderer.tsx +31 -20
  71. package/src/lib/utils/config-resolver.ts +71 -0
  72. package/src/registry/BlockRegistry.tsx +255 -0
  73. package/src/state/EditorContext.tsx +43 -8
  74. package/src/state/types.ts +16 -0
  75. package/src/types/block.ts +10 -0
  76. package/src/types/newsletter.ts +5 -0
  77. package/src/views/CanvasEditor/BlockWrapper.tsx +27 -2
  78. package/src/views/CanvasEditor/CanvasEditorView.tsx +142 -61
  79. package/src/views/CanvasEditor/EditorBody.tsx +17 -13
  80. package/src/views/CanvasEditor/components/EditorCanvas.tsx +178 -115
  81. package/src/views/CanvasEditor/components/EditorSidebar.tsx +57 -2
  82. package/src/views/CanvasEditor/hooks/useRegisteredBlocks.ts +6 -45
  83. package/src/views/NewsletterManager.tsx +164 -6
  84. package/src/views/components/DomainPromptModal.tsx +160 -0
  85. package/src/views/components/NewsletterCard.tsx +212 -0
  86. package/src/views/components/NewsletterGrid.tsx +48 -0
  87. package/src/views/components/SendNewsletterModal.tsx +270 -184
  88. package/src/views/components/SmtpSettingsModal.tsx +11 -0
  89. package/src/views/components/TestEmailModal.tsx +235 -149
  90. package/src/registry/BlockRegistry.ts +0 -53
@@ -29,7 +29,7 @@ export interface CanvasEditorViewProps {
29
29
  }
30
30
 
31
31
  export function CanvasEditorView({ newsletterId, darkMode, backgroundColors: propsBackgroundColors, siteId, locale, isWelcomeEmail }: CanvasEditorViewProps) {
32
- const { state, helpers, dispatch, darkMode: contextDarkMode, backgroundColors: contextBackgroundColors, canUndo, canRedo } = useEditor();
32
+ const { state, helpers, dispatch, darkMode: contextDarkMode, backgroundColors: contextBackgroundColors, emailConfig: contextEmailConfig, canUndo, canRedo } = useEditor();
33
33
  const effectiveDarkMode = darkMode !== undefined ? darkMode : contextDarkMode;
34
34
  const effectiveBackgroundColors = propsBackgroundColors || contextBackgroundColors;
35
35
  const [isSidebarOpen, setSidebarOpen] = useState(true);
@@ -42,6 +42,13 @@ export function CanvasEditorView({ newsletterId, darkMode, backgroundColors: pro
42
42
  const [isLoadingLanguage, setIsLoadingLanguage] = useState(true);
43
43
  const [availableLanguages, setAvailableLanguages] = useState<string[]>([locale || 'en']);
44
44
  const [currentLanguage, setCurrentLanguage] = useState<string>(locale || 'en');
45
+
46
+ // Global branding settings from SMTP
47
+ const [globalUnsubscribeTranslations, setGlobalUnsubscribeTranslations] = useState<Record<string, string>>({
48
+ en: '',
49
+ nl: '',
50
+ sv: '',
51
+ });
45
52
 
46
53
  // Get registered blocks
47
54
  const registeredBlocks = useRegisteredBlocks();
@@ -97,6 +104,25 @@ export function CanvasEditorView({ newsletterId, darkMode, backgroundColors: pro
97
104
  setPrimaryLanguage(primary);
98
105
  setAvailableLanguages(available);
99
106
  setCurrentLanguage(primary);
107
+
108
+ // Load global unsubscribe translations
109
+ if (smtpData.unsubscribeTranslations) {
110
+ setGlobalUnsubscribeTranslations(smtpData.unsubscribeTranslations);
111
+ }
112
+
113
+ // Update emailConfig with logo from SMTP settings if available
114
+ if (smtpData.logoUrl) {
115
+ if (typeof window !== 'undefined') {
116
+ if (!(window as any).__JHITS_PLUGIN_PROPS__) (window as any).__JHITS_PLUGIN_PROPS__ = {};
117
+ if (!(window as any).__JHITS_PLUGIN_PROPS__['plugin-newsletter']) (window as any).__JHITS_PLUGIN_PROPS__['plugin-newsletter'] = {};
118
+
119
+ const currentConfig = (window as any).__JHITS_PLUGIN_PROPS__['plugin-newsletter'].emailConfig || {};
120
+ (window as any).__JHITS_PLUGIN_PROPS__['plugin-newsletter'].emailConfig = {
121
+ ...currentConfig,
122
+ logoUrl: smtpData.logoUrl
123
+ };
124
+ }
125
+ }
100
126
  } catch (error) {
101
127
  console.error('Failed to fetch language settings:', error);
102
128
  } finally {
@@ -106,6 +132,41 @@ export function CanvasEditorView({ newsletterId, darkMode, backgroundColors: pro
106
132
  fetchLanguageSettings();
107
133
  }, []);
108
134
 
135
+ // Handle global unsubscribe text update
136
+ const handleGlobalUnsubscribeUpdate = async (text: string) => {
137
+ const newTranslations = {
138
+ ...globalUnsubscribeTranslations,
139
+ [currentLanguage]: text
140
+ };
141
+
142
+ // Optimistic update
143
+ setGlobalUnsubscribeTranslations(newTranslations);
144
+
145
+ // Update window props for immediate preview reflection
146
+ if (typeof window !== 'undefined') {
147
+ if (!(window as any).__JHITS_PLUGIN_PROPS__) (window as any).__JHITS_PLUGIN_PROPS__ = {};
148
+ if (!(window as any).__JHITS_PLUGIN_PROPS__['plugin-newsletter']) (window as any).__JHITS_PLUGIN_PROPS__['plugin-newsletter'] = {};
149
+ (window as any).__JHITS_PLUGIN_PROPS__['plugin-newsletter'].unsubscribeTranslations = newTranslations;
150
+ }
151
+
152
+ try {
153
+ // We need current SMTP settings to save along with the new translation
154
+ const smtpResponse = await fetch('/api/plugin-newsletter/smtp');
155
+ const smtpData = await smtpResponse.json();
156
+
157
+ await fetch('/api/plugin-newsletter/smtp', {
158
+ method: 'POST',
159
+ headers: { 'Content-Type': 'application/json' },
160
+ body: JSON.stringify({
161
+ ...smtpData,
162
+ unsubscribeTranslations: newTranslations
163
+ }),
164
+ });
165
+ } catch (error) {
166
+ console.error('Failed to save global unsubscribe translation:', error);
167
+ }
168
+ };
169
+
109
170
  // Handle language change for welcome email
110
171
  const handleLanguageChange = async (newLanguage: string) => {
111
172
  // Save current content first if dirty
@@ -210,17 +271,14 @@ export function CanvasEditorView({ newsletterId, darkMode, backgroundColors: pro
210
271
  }
211
272
 
212
273
  // Replace the block in place by updating the blocks array directly
213
- // This preserves the position and ID
214
274
  const newBlocks = state.blocks.map((block, idx) => {
215
275
  if (idx === blockIndex) {
216
- // Replace this block with the new type
217
276
  return {
218
277
  ...block,
219
278
  type: blockType,
220
279
  data: { ...blockDefinition.defaultData },
221
280
  };
222
281
  }
223
- // Keep all other blocks unchanged
224
282
  return block;
225
283
  });
226
284
  dispatch({ type: 'SET_BLOCKS', payload: newBlocks });
@@ -232,12 +290,29 @@ export function CanvasEditorView({ newsletterId, darkMode, backgroundColors: pro
232
290
  // Slash command hook
233
291
  const slashCommand = useSlashCommand(registeredBlocks, handleSlashCommandSelect);
234
292
 
293
+ // Lock dashboard scroll when editor is active to ensure our internal scrolling works
294
+ useEffect(() => {
295
+ if (typeof window === 'undefined') return;
296
+
297
+ const dashboardContainer = document.querySelector('.custom-scrollbar.overflow-y-auto');
298
+ const originalOverflow = dashboardContainer ? (dashboardContainer as HTMLElement).style.overflow : '';
299
+
300
+ if (dashboardContainer) {
301
+ (dashboardContainer as HTMLElement).style.overflow = 'hidden';
302
+ }
303
+
304
+ return () => {
305
+ if (dashboardContainer) {
306
+ (dashboardContainer as HTMLElement).style.overflow = originalOverflow;
307
+ }
308
+ };
309
+ }, []);
310
+
235
311
  // Handle save
236
312
  const handleSave = async () => {
237
313
  setIsSaving(true);
238
314
  setSaveError(null);
239
315
  try {
240
- // Always pass language for saving (both welcome email and regular newsletters)
241
316
  await helpers.save({ language: currentLanguage });
242
317
  setIsSaving(false);
243
318
  } catch (error: any) {
@@ -264,7 +339,7 @@ export function CanvasEditorView({ newsletterId, darkMode, backgroundColors: pro
264
339
  }
265
340
 
266
341
  return (
267
- <div className="h-full rounded-[2.5rem] w-full bg-dashboard-card text-dashboard-text flex flex-col font-sans transition-colors duration-300 overflow-hidden relative">
342
+ <div className="absolute inset-0 flex flex-col font-sans transition-colors duration-300 overflow-hidden bg-white dark:bg-neutral-950">
268
343
  {isLoadingLanguage ? (
269
344
  <div className="h-full w-full flex items-center justify-center">
270
345
  <div className="text-center">
@@ -273,58 +348,58 @@ export function CanvasEditorView({ newsletterId, darkMode, backgroundColors: pro
273
348
  </div>
274
349
  </div>
275
350
  ) : (
276
- <main className="flex flex-1 flex-col relative min-h-0">
351
+ <main className="flex flex-1 flex-col relative min-h-0 overflow-hidden">
277
352
  {/* Error Banner */}
278
353
  <ErrorBanner error={saveError} onDismiss={() => setSaveError(null)} />
279
354
 
280
- <EditorHeader
281
- isPreviewMode={isPreviewMode}
282
- onPreviewToggle={() => setIsPreviewMode(!isPreviewMode)}
283
- isSidebarOpen={isSidebarOpen}
284
- onSidebarToggle={() => setSidebarOpen(!isSidebarOpen)}
285
- isSaving={isSaving}
286
- onSave={handleSave}
287
- onSaveError={(error) => {
288
- if (error) {
289
- setSaveError(error);
290
- } else {
291
- setSaveError(null);
292
- }
293
- }}
294
- isDirty={state.isDirty}
295
- isWelcomeEmail={isWelcomeEmail}
296
- languages={availableLanguages}
297
- currentLanguage={currentLanguage}
298
- onLanguageChange={handleLanguageChange}
299
- onAddLanguage={handleAddLanguage}
300
- />
301
-
302
- {/* Editor Content Wrapper */}
303
- <div className="flex flex-1 relative overflow-hidden min-h-0 flex-nowrap">
304
- {/* CENTER: THE WRITING CANVAS */}
305
- <EditorCanvas
355
+ {/* Sticky Header */}
356
+ <div className="flex-none z-30 border-b border-dashboard-border">
357
+ <EditorHeader
306
358
  isPreviewMode={isPreviewMode}
307
- contentBlocks={state.blocks}
308
- title={state.title}
309
- siteId={siteId}
310
- locale={locale}
311
- darkMode={effectiveDarkMode}
312
- backgroundColors={effectiveBackgroundColors}
313
- metadata={state.metadata}
314
- onTitleChange={(title: string) => dispatch({ type: 'SET_TITLE', payload: title })}
315
- onMetadataChange={(metadata) => dispatch({ type: 'SET_METADATA', payload: metadata })}
316
- onBlockAdd={(type: string, index: number, containerId?: string) => helpers.addBlock(type, index, containerId)}
317
- onBlockUpdate={(id: string, data: Partial<Block['data']>) => helpers.updateBlock(id, data)}
318
- onBlockDelete={(id: string) => helpers.deleteBlock(id)}
319
- onBlockMove={(id: string, newIndex: number, containerId?: string) => helpers.moveBlock(id, newIndex, containerId)}
320
- slashCommand={slashCommand}
359
+ onPreviewToggle={() => setIsPreviewMode(!isPreviewMode)}
360
+ isSidebarOpen={isSidebarOpen}
361
+ onSidebarToggle={() => setSidebarOpen(!isSidebarOpen)}
362
+ isSaving={isSaving}
363
+ onSave={handleSave}
364
+ onSaveError={(error) => setSaveError(error || null)}
365
+ isDirty={state.isDirty}
366
+ isWelcomeEmail={isWelcomeEmail}
367
+ languages={availableLanguages}
368
+ currentLanguage={currentLanguage}
369
+ onLanguageChange={handleLanguageChange}
370
+ onAddLanguage={handleAddLanguage}
321
371
  />
372
+ </div>
373
+
374
+ {/* Editor Content Wrapper - Independently Scrollable Area */}
375
+ <div className="flex-1 flex relative overflow-hidden min-h-0 flex-nowrap">
376
+ {/* CENTER: THE WRITING CANVAS */}
377
+ <div className="flex-1 overflow-hidden h-full">
378
+ <EditorCanvas
379
+ isPreviewMode={isPreviewMode}
380
+ contentBlocks={state.blocks}
381
+ title={state.title}
382
+ siteId={siteId}
383
+ locale={currentLanguage}
384
+ darkMode={effectiveDarkMode}
385
+ backgroundColors={effectiveBackgroundColors}
386
+ metadata={state.metadata}
387
+ onTitleChange={(title: string) => dispatch({ type: 'SET_TITLE', payload: title })}
388
+ onMetadataChange={(metadata) => dispatch({ type: 'SET_METADATA', payload: metadata })}
389
+ onBlockAdd={(type: string, index: number, containerId?: string) => helpers.addBlock(type, index, containerId)}
390
+ onBlockUpdate={(id: string, data: Partial<Block['data']>) => helpers.updateBlock(id, data)}
391
+ onBlockDelete={(id: string) => helpers.deleteBlock(id)}
392
+ onBlockMove={(id: string, newIndex: number, containerId?: string) => helpers.moveBlock(id, newIndex, containerId)}
393
+ slashCommand={slashCommand}
394
+ />
395
+ </div>
322
396
 
323
397
  {/* RIGHT SIDEBAR: THE "DESK" (SETTINGS) */}
324
398
  {!isPreviewMode && (
325
399
  <aside
326
- className={`transition-all duration-500 ease-[cubic-bezier(0.4,0,0.2,1)] border-l border-dashboard-border bg-dashboard-sidebar overflow-y-auto overflow-x-hidden h-full ${isSidebarOpen ? 'w-80' : 'w-0 opacity-0 pointer-events-none'
327
- }`}
400
+ className={`flex-none transition-all duration-500 ease-[cubic-bezier(0.4,0,0.2,1)] border-l border-dashboard-border bg-dashboard-sidebar overflow-y-auto custom-scrollbar h-full ${
401
+ isSidebarOpen ? 'w-80' : 'w-0 opacity-0 pointer-events-none border-l-0'
402
+ }`}
328
403
  >
329
404
  <EditorSidebar
330
405
  metadata={state.metadata}
@@ -335,6 +410,8 @@ export function CanvasEditorView({ newsletterId, darkMode, backgroundColors: pro
335
410
  languages={availableLanguages}
336
411
  currentLanguage={currentLanguage}
337
412
  onLanguageChange={handleLanguageChange}
413
+ globalUnsubscribeText={globalUnsubscribeTranslations[currentLanguage]}
414
+ onGlobalUnsubscribeUpdate={handleGlobalUnsubscribeUpdate}
338
415
  />
339
416
  </aside>
340
417
  )}
@@ -342,19 +419,23 @@ export function CanvasEditorView({ newsletterId, darkMode, backgroundColors: pro
342
419
 
343
420
  {/* Slash Command Menu */}
344
421
  {slashCommand.isOpen && slashCommand.position && (
345
- <SlashCommandMenu
346
- blocks={slashCommand.filteredBlocks}
347
- query={slashCommand.query}
348
- selectedIndex={slashCommand.selectedIndex}
349
- onSelect={(replaceBlockId) => {
350
- // Use the replaceBlockId passed from the menu (which comes from state)
351
- // This ensures we use the correct block ID that was set when the menu opened
352
- slashCommand.selectCurrent(replaceBlockId);
422
+ <div
423
+ className="fixed z-50 transition-all duration-200"
424
+ style={{
425
+ left: slashCommand.position.left,
426
+ top: slashCommand.position.top
353
427
  }}
354
- position={slashCommand.position}
355
- onClose={slashCommand.closeMenu}
356
- replaceBlockId={slashCommand.replaceBlockId}
357
- />
428
+ >
429
+ <SlashCommandMenu
430
+ blocks={slashCommand.filteredBlocks}
431
+ query={slashCommand.query}
432
+ selectedIndex={slashCommand.selectedIndex}
433
+ onSelect={(replaceBlockId) => slashCommand.selectCurrent(replaceBlockId)}
434
+ position={slashCommand.position}
435
+ onClose={slashCommand.closeMenu}
436
+ replaceBlockId={slashCommand.replaceBlockId}
437
+ />
438
+ </div>
358
439
  )}
359
440
  </main>
360
441
  )}
@@ -53,14 +53,18 @@ export function EditorBody({
53
53
  {blocks.map((block, index) => (
54
54
  <React.Fragment key={block.id}>
55
55
  {/* Add Block Button Above */}
56
- <div className="relative h-2 group/add-button">
57
- <button
58
- onClick={() => onBlockAdd('paragraph', index)}
59
- className="absolute left-0 top-1/2 -translate-y-1/2 w-6 h-6 flex items-center justify-center opacity-0 group-hover/add-button:opacity-100 transition-opacity hover:bg-neutral-100 dark:hover:bg-neutral-800 rounded"
60
- title="Add block"
61
- >
62
- <Plus size={14} className="text-neutral-400 dark:text-neutral-500" />
63
- </button>
56
+ <div className="relative h-4 group/add-button">
57
+ <div className="absolute inset-x-0 top-1/2 -translate-y-1/2 flex items-center justify-center">
58
+ <div className="w-full h-[1px] bg-neutral-100 dark:bg-neutral-800 opacity-0 group-hover/add-button:opacity-100 transition-opacity" />
59
+ <button
60
+ onClick={() => onBlockAdd('paragraph', index)}
61
+ className="mx-4 flex-shrink-0 w-6 h-6 flex items-center justify-center opacity-0 group-hover/add-button:opacity-100 transition-all hover:bg-primary hover:text-white bg-white dark:bg-neutral-800 border border-neutral-200 dark:border-neutral-700 shadow-sm rounded-full z-10"
62
+ title="Add block here"
63
+ >
64
+ <Plus size={14} />
65
+ </button>
66
+ <div className="w-full h-[1px] bg-neutral-100 dark:bg-neutral-800 opacity-0 group-hover/add-button:opacity-100 transition-opacity" />
67
+ </div>
64
68
  </div>
65
69
 
66
70
  {/* Block */}
@@ -80,14 +84,14 @@ export function EditorBody({
80
84
  </React.Fragment>
81
85
  ))}
82
86
 
83
- {/* Add Block Button at Bottom */}
84
- <div className="relative h-8 group/add-button-bottom mt-2">
87
+ {/* Add Block Button at Bottom - Subtle Plus on the left */}
88
+ <div className="relative h-12 mt-4 group/add-bottom">
85
89
  <button
86
90
  onClick={() => onBlockAdd('paragraph', blocks.length)}
87
- className="absolute left-0 top-1/2 -translate-y-1/2 w-6 h-6 flex items-center justify-center opacity-0 group-hover/add-button-bottom:opacity-100 transition-opacity hover:bg-neutral-100 dark:hover:bg-neutral-800 rounded"
88
- title="Add block"
91
+ className="absolute left-0 top-1/2 -translate-y-1/2 w-8 h-8 flex items-center justify-center opacity-20 group-hover/add-bottom:opacity-100 hover:bg-neutral-100 dark:hover:bg-neutral-800 rounded-full transition-all text-neutral-500"
92
+ title="Add block at the end"
89
93
  >
90
- <Plus size={14} className="text-neutral-400 dark:text-neutral-500" />
94
+ <Plus size={18} />
91
95
  </button>
92
96
  </div>
93
97
  </div>