@jhits/plugin-newsletter 0.0.8 → 0.0.9

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 (75) hide show
  1. package/data/locales/en/common.json +12 -0
  2. package/data/locales/nl/common.json +12 -0
  3. package/data/locales/sv/common.json +12 -0
  4. package/dist/api/email-utils.d.ts +6 -0
  5. package/dist/api/email-utils.d.ts.map +1 -0
  6. package/dist/api/email-utils.js +134 -0
  7. package/dist/api/handler.d.ts +2 -47
  8. package/dist/api/handler.d.ts.map +1 -1
  9. package/dist/api/handler.js +2 -523
  10. package/dist/api/handlers/index.d.ts +12 -0
  11. package/dist/api/handlers/index.d.ts.map +1 -0
  12. package/dist/api/handlers/index.js +11 -0
  13. package/dist/api/handlers/newsletters.d.ts +11 -0
  14. package/dist/api/handlers/newsletters.d.ts.map +1 -0
  15. package/dist/api/handlers/newsletters.js +250 -0
  16. package/dist/api/handlers/send-newsletter.d.ts +8 -0
  17. package/dist/api/handlers/send-newsletter.d.ts.map +1 -0
  18. package/dist/api/handlers/send-newsletter.js +206 -0
  19. package/dist/api/handlers/settings.d.ts +11 -0
  20. package/dist/api/handlers/settings.d.ts.map +1 -0
  21. package/dist/api/handlers/settings.js +304 -0
  22. package/dist/api/handlers/subscribers.d.ts +10 -0
  23. package/dist/api/handlers/subscribers.d.ts.map +1 -0
  24. package/dist/api/handlers/subscribers.js +96 -0
  25. package/dist/api/handlers/upload.d.ts +7 -0
  26. package/dist/api/handlers/upload.d.ts.map +1 -0
  27. package/dist/api/handlers/upload.js +32 -0
  28. package/dist/api/handlers/welcome-email.d.ts +13 -0
  29. package/dist/api/handlers/welcome-email.d.ts.map +1 -0
  30. package/dist/api/handlers/welcome-email.js +142 -0
  31. package/dist/api/router.d.ts.map +1 -1
  32. package/dist/api/router.js +45 -6
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +42 -16
  35. package/dist/lib/email/EmailRenderer.d.ts.map +1 -1
  36. package/dist/lib/email/EmailRenderer.js +3 -7
  37. package/dist/lib/i18n.d.ts +16 -0
  38. package/dist/lib/i18n.d.ts.map +1 -0
  39. package/dist/lib/i18n.js +60 -0
  40. package/dist/state/EditorContext.d.ts +3 -1
  41. package/dist/state/EditorContext.d.ts.map +1 -1
  42. package/dist/state/EditorContext.js +1 -5
  43. package/dist/state/reducer.js +5 -5
  44. package/dist/types/newsletter.d.ts +1 -0
  45. package/dist/types/newsletter.d.ts.map +1 -1
  46. package/dist/views/CanvasEditor/CanvasEditorView.d.ts +4 -2
  47. package/dist/views/CanvasEditor/CanvasEditorView.d.ts.map +1 -1
  48. package/dist/views/CanvasEditor/CanvasEditorView.js +73 -10
  49. package/dist/views/CanvasEditor/EditorHeader.d.ts +6 -1
  50. package/dist/views/CanvasEditor/EditorHeader.d.ts.map +1 -1
  51. package/dist/views/CanvasEditor/EditorHeader.js +66 -11
  52. package/dist/views/CanvasEditor/components/EditorSidebar.d.ts +6 -2
  53. package/dist/views/CanvasEditor/components/EditorSidebar.d.ts.map +1 -1
  54. package/dist/views/CanvasEditor/components/EditorSidebar.js +3 -3
  55. package/dist/views/CanvasEditor/hooks/useNewsletterLoader.d.ts +1 -1
  56. package/dist/views/CanvasEditor/hooks/useNewsletterLoader.d.ts.map +1 -1
  57. package/dist/views/CanvasEditor/hooks/useNewsletterLoader.js +41 -5
  58. package/dist/views/NewsletterEditor.d.ts +4 -2
  59. package/dist/views/NewsletterEditor.d.ts.map +1 -1
  60. package/dist/views/NewsletterEditor.js +2 -2
  61. package/dist/views/NewsletterManager.d.ts.map +1 -1
  62. package/dist/views/NewsletterManager.js +137 -8
  63. package/dist/views/components/SendNewsletterModal.d.ts +18 -0
  64. package/dist/views/components/SendNewsletterModal.d.ts.map +1 -0
  65. package/dist/views/components/SendNewsletterModal.js +107 -0
  66. package/dist/views/components/SmtpSettingsModal.d.ts +10 -0
  67. package/dist/views/components/SmtpSettingsModal.d.ts.map +1 -0
  68. package/dist/views/components/SmtpSettingsModal.js +145 -0
  69. package/dist/views/components/TestEmailModal.d.ts +10 -0
  70. package/dist/views/components/TestEmailModal.d.ts.map +1 -0
  71. package/dist/views/components/TestEmailModal.js +99 -0
  72. package/package.json +20 -9
  73. package/templates/logo.png +0 -0
  74. package/templates/test-email.hbs +221 -0
  75. package/templates/welcome-email-legacy.hbs +35 -0
@@ -4,7 +4,7 @@
4
4
  */
5
5
  'use server';
6
6
  import { NextResponse } from 'next/server';
7
- import { GET_SUBSCRIBERS, POST_SUBSCRIBE, GET_SUBSCRIBER, DELETE_SUBSCRIBER, GET_SETTINGS, POST_SETTINGS, GET_NEWSLETTERS, GET_NEWSLETTER, POST_NEWSLETTER, PUT_NEWSLETTER, DELETE_NEWSLETTER, } from './handler';
7
+ import { GET_SUBSCRIBERS, POST_SUBSCRIBE, GET_SUBSCRIBER, DELETE_SUBSCRIBER, GET_SETTINGS, POST_SETTINGS, GET_NEWSLETTERS, GET_NEWSLETTER, POST_NEWSLETTER, PUT_NEWSLETTER, DELETE_NEWSLETTER, GET_WELCOME_EMAIL_STATUS, GET_WELCOME_EMAIL, POST_WELCOME_EMAIL, GET_SMTP_SETTINGS, POST_SMTP_SETTINGS, POST_TEST_EMAIL, POST_LOGO_UPLOAD, POST_SEND_NEWSLETTER, GET_NEWSLETTER_FOR_SEND, } from './handler';
8
8
  /**
9
9
  * Handle newsletter API requests
10
10
  */
@@ -50,16 +50,24 @@ export async function handleNewsletterApi(req, path, config) {
50
50
  return await POST_NEWSLETTER(req, config);
51
51
  }
52
52
  if (path[1]) {
53
- // /api/plugin-newsletter/newsletters/[slug]
54
- const newsletterSlug = decodeURIComponent(path[1]);
53
+ const newsletterIdOrSlug = decodeURIComponent(path[1]);
54
+ // Special route: /api/plugin-newsletter/newsletters/[id]/send
55
+ if (path[2] === 'send' && method === 'POST') {
56
+ return await POST_SEND_NEWSLETTER(req, newsletterIdOrSlug, config);
57
+ }
58
+ // Special route: /api/plugin-newsletter/newsletters/[id]/send (GET for info)
59
+ if (path[2] === 'send' && method === 'GET') {
60
+ return await GET_NEWSLETTER_FOR_SEND(req, newsletterIdOrSlug, config);
61
+ }
62
+ // /api/plugin-newsletter/newsletters/[id-or-slug]
55
63
  if (method === 'GET') {
56
- return await GET_NEWSLETTER(req, newsletterSlug, config);
64
+ return await GET_NEWSLETTER(req, newsletterIdOrSlug, config);
57
65
  }
58
66
  if (method === 'PUT') {
59
- return await PUT_NEWSLETTER(req, newsletterSlug, config);
67
+ return await PUT_NEWSLETTER(req, newsletterIdOrSlug, config);
60
68
  }
61
69
  if (method === 'DELETE') {
62
- return await DELETE_NEWSLETTER(req, newsletterSlug, config);
70
+ return await DELETE_NEWSLETTER(req, newsletterIdOrSlug, config);
63
71
  }
64
72
  }
65
73
  else {
@@ -72,6 +80,37 @@ export async function handleNewsletterApi(req, path, config) {
72
80
  }
73
81
  }
74
82
  }
83
+ // Route: /api/plugin-newsletter/welcome-email
84
+ if (route === 'welcome-email') {
85
+ // Status endpoint: /api/plugin-newsletter/welcome-email/status
86
+ if (path[1] === 'status' && method === 'GET') {
87
+ return await GET_WELCOME_EMAIL_STATUS(req, config);
88
+ }
89
+ // Welcome email content: /api/plugin-newsletter/welcome-email
90
+ if (method === 'GET') {
91
+ return await GET_WELCOME_EMAIL(req, config);
92
+ }
93
+ if (method === 'POST' || method === 'PUT') {
94
+ return await POST_WELCOME_EMAIL(req, config);
95
+ }
96
+ }
97
+ // Route: /api/plugin-newsletter/smtp
98
+ if (route === 'smtp') {
99
+ // Test email endpoint: /api/plugin-newsletter/smtp/test
100
+ if (path[1] === 'test' && method === 'POST') {
101
+ return await POST_TEST_EMAIL(req, config);
102
+ }
103
+ if (method === 'GET') {
104
+ return await GET_SMTP_SETTINGS(req, config);
105
+ }
106
+ if (method === 'POST' || method === 'PUT') {
107
+ return await POST_SMTP_SETTINGS(req, config);
108
+ }
109
+ }
110
+ // Route: /api/plugin-newsletter/upload
111
+ if (route === 'upload' && method === 'POST') {
112
+ return await POST_LOGO_UPLOAD(req, config);
113
+ }
75
114
  // Method not allowed
76
115
  return NextResponse.json({ error: `Method ${method} not allowed for route: ${route || '/'}` }, { status: 405 });
77
116
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAOtD;;;GAGG;AACH,MAAM,WAAW,WAAW;IACxB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,yGAAyG;IACzG,YAAY,CAAC,EAAE,qBAAqB,EAAE,CAAC;IACvC,qEAAqE;IACrE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uCAAuC;IACvC,gBAAgB,CAAC,EAAE;QACf,iDAAiD;QACjD,KAAK,EAAE,MAAM,CAAC;QACd,gDAAgD;QAChD,IAAI,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;CACL;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,OAAO,UAAU,gBAAgB,CAAC,KAAK,EAAE,WAAW,2CAsO1D;AAGD,OAAO,EAAE,gBAAgB,IAAI,KAAK,EAAE,CAAC;AAGrC,YAAY,EACR,KAAK,EACL,mBAAmB,EACnB,qBAAqB,EACrB,wBAAwB,EACxB,cAAc,EACd,iBAAiB,EACjB,eAAe,GAClB,MAAM,eAAe,CAAC;AAGvB,YAAY,EACR,UAAU,EACV,gBAAgB,EAChB,kBAAkB,EAClB,kBAAkB,EAClB,uBAAuB,GAC1B,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EAAE,oBAAoB,EAAE,MAAM,QAAQ,CAAC;AAC9C,YAAY,EAAE,sBAAsB,EAAE,MAAM,QAAQ,CAAC;AAGrD,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClE,YAAY,EAAE,mBAAmB,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAGpF,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAOtD;;;GAGG;AACH,MAAM,WAAW,WAAW;IACxB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,yGAAyG;IACzG,YAAY,CAAC,EAAE,qBAAqB,EAAE,CAAC;IACvC,qEAAqE;IACrE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uCAAuC;IACvC,gBAAgB,CAAC,EAAE;QACf,iDAAiD;QACjD,KAAK,EAAE,MAAM,CAAC;QACd,gDAAgD;QAChD,IAAI,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;CACL;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,OAAO,UAAU,gBAAgB,CAAC,KAAK,EAAE,WAAW,2CA8Q1D;AAGD,OAAO,EAAE,gBAAgB,IAAI,KAAK,EAAE,CAAC;AAGrC,YAAY,EACR,KAAK,EACL,mBAAmB,EACnB,qBAAqB,EACrB,wBAAwB,EACxB,cAAc,EACd,iBAAiB,EACjB,eAAe,GAClB,MAAM,eAAe,CAAC;AAGvB,YAAY,EACR,UAAU,EACV,gBAAgB,EAChB,kBAAkB,EAClB,kBAAkB,EAClB,uBAAuB,GAC1B,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EAAE,oBAAoB,EAAE,MAAM,QAAQ,CAAC;AAC9C,YAAY,EAAE,sBAAsB,EAAE,MAAM,QAAQ,CAAC;AAGrD,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClE,YAAY,EAAE,mBAAmB,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAGpF,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -120,15 +120,20 @@ export default function NewsletterPlugin(props) {
120
120
  case 'newsletters':
121
121
  return _jsx(NewsletterManagerView, { siteId: siteId, locale: locale });
122
122
  case 'editor':
123
- const newsletterSlug = subPath[1];
124
- return (_jsx(EditorProvider, { customBlocks: customBlocks, darkMode: darkMode, backgroundColors: backgroundColors, onSave: async (state) => {
123
+ const newsletterId = subPath[1];
124
+ return (_jsx(EditorProvider, { customBlocks: customBlocks, darkMode: darkMode, backgroundColors: backgroundColors, onSave: async (state, extraData) => {
125
125
  // Save to API - create new or update existing newsletter
126
- const originalSlug = newsletterSlug || state.slug;
126
+ const originalId = newsletterId || state.slug;
127
127
  const apiData = editorStateToAPI(state);
128
- // If we have a slug, try to update first
129
- if (originalSlug) {
130
- console.log('[NewsletterPlugin] Attempting to update newsletter with slug:', originalSlug);
131
- const updateResponse = await fetch(`/api/plugin-newsletter/newsletters/${originalSlug}`, {
128
+ // Include language in metadata if provided
129
+ if (extraData?.language) {
130
+ apiData.metadata = apiData.metadata || {};
131
+ apiData.metadata.lang = extraData.language;
132
+ }
133
+ // If we have an id, try to update first
134
+ if (originalId) {
135
+ console.log('[NewsletterPlugin] Attempting to update newsletter with id:', originalId);
136
+ const updateResponse = await fetch(`/api/plugin-newsletter/newsletters/${originalId}`, {
132
137
  method: 'PUT',
133
138
  headers: { 'Content-Type': 'application/json' },
134
139
  credentials: 'include',
@@ -136,9 +141,9 @@ export default function NewsletterPlugin(props) {
136
141
  });
137
142
  if (updateResponse.ok) {
138
143
  const result = await updateResponse.json();
139
- // If the slug changed, update the URL
140
- if (result.slug && result.slug !== originalSlug) {
141
- window.history.replaceState(null, '', `/dashboard/newsletter/editor/${result.slug}`);
144
+ // If the id changed, update the URL
145
+ if (result.id && result.id !== originalId) {
146
+ window.history.replaceState(null, '', `/dashboard/newsletter/editor/${result.id}`);
142
147
  }
143
148
  return result;
144
149
  }
@@ -158,7 +163,7 @@ export default function NewsletterPlugin(props) {
158
163
  throw new Error(errorMessage);
159
164
  }
160
165
  }
161
- // Create new newsletter (either no slug or update returned 404)
166
+ // Create new newsletter (either no id or update returned 404)
162
167
  console.log('[NewsletterPlugin] Creating new newsletter');
163
168
  const createResponse = await fetch('/api/plugin-newsletter/newsletters/new', {
164
169
  method: 'POST',
@@ -177,16 +182,21 @@ export default function NewsletterPlugin(props) {
177
182
  throw new Error(errorMessage);
178
183
  }
179
184
  const result = await createResponse.json();
180
- // Update the URL to the new newsletter's slug
181
- if (result.slug) {
182
- window.history.replaceState(null, '', `/dashboard/newsletter/editor/${result.slug}`);
185
+ // Update the URL to the new newsletter's id
186
+ if (result.id) {
187
+ window.history.replaceState(null, '', `/dashboard/newsletter/editor/${result.id}`);
183
188
  }
184
189
  return result;
185
- }, children: _jsx(NewsletterEditorView, { newsletterSlug: newsletterSlug, siteId: siteId, locale: locale, darkMode: darkMode, backgroundColors: backgroundColors }) }));
190
+ }, children: _jsx(NewsletterEditorView, { newsletterId: newsletterId, siteId: siteId, locale: locale, darkMode: darkMode, backgroundColors: backgroundColors }) }));
186
191
  case 'new':
187
- return (_jsx(EditorProvider, { customBlocks: customBlocks, darkMode: darkMode, backgroundColors: backgroundColors, onSave: async (state) => {
192
+ return (_jsx(EditorProvider, { customBlocks: customBlocks, darkMode: darkMode, backgroundColors: backgroundColors, onSave: async (state, extraData) => {
188
193
  // Save to API - create new newsletter
189
194
  const apiData = editorStateToAPI(state);
195
+ // Include language in metadata if provided
196
+ if (extraData?.language) {
197
+ apiData.metadata = apiData.metadata || {};
198
+ apiData.metadata.lang = extraData.language;
199
+ }
190
200
  const response = await fetch('/api/plugin-newsletter/newsletters/new', {
191
201
  method: 'POST',
192
202
  headers: { 'Content-Type': 'application/json' },
@@ -206,6 +216,22 @@ export default function NewsletterPlugin(props) {
206
216
  }, children: _jsx(NewsletterEditorView, { siteId: siteId, locale: locale, darkMode: darkMode, backgroundColors: backgroundColors }) }));
207
217
  case 'subscribers':
208
218
  return _jsx(SubscribersView, { siteId: siteId, locale: locale });
219
+ case 'welcome':
220
+ return (_jsx(EditorProvider, { customBlocks: customBlocks, darkMode: darkMode, backgroundColors: backgroundColors, isWelcomeEmail: true, onSave: async (state, extraData) => {
221
+ const apiData = editorStateToAPI(state);
222
+ const language = extraData?.language || locale || 'en';
223
+ const response = await fetch(`/api/plugin-newsletter/welcome-email?language=${language}`, {
224
+ method: 'POST',
225
+ headers: { 'Content-Type': 'application/json' },
226
+ credentials: 'include',
227
+ body: JSON.stringify(apiData),
228
+ });
229
+ if (!response.ok) {
230
+ const error = await response.json();
231
+ throw new Error(error.message || 'Failed to save welcome email');
232
+ }
233
+ return await response.json();
234
+ }, children: _jsx(NewsletterEditorView, { siteId: siteId, locale: locale, darkMode: darkMode, backgroundColors: backgroundColors, isWelcomeEmail: true }) }));
209
235
  case 'settings':
210
236
  return _jsx(SettingsView, { siteId: siteId, locale: locale });
211
237
  default:
@@ -1 +1 @@
1
- {"version":3,"file":"EmailRenderer.d.ts","sourceRoot":"","sources":["../../../src/lib/email/EmailRenderer.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAI1C;;GAEG;AACH,MAAM,WAAW,kBAAkB;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CAC1B;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,GAAE,kBAAuB,GAAG,MAAM,CAwBzF;AA0OD;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,OAAO,GAAE,kBAAuB,GAAG,MAAM,CAE7F;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CACvC,MAAM,EAAE,KAAK,EAAE,EACf,QAAQ,EAAE;IACN,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB,EACD,OAAO,EAAE,kBAAkB,GAAG;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB,GACF,MAAM,CA8GR"}
1
+ {"version":3,"file":"EmailRenderer.d.ts","sourceRoot":"","sources":["../../../src/lib/email/EmailRenderer.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAI1C;;GAEG;AACH,MAAM,WAAW,kBAAkB;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CAC1B;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,GAAE,kBAAuB,GAAG,MAAM,CAmBzF;AA0OD;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,OAAO,GAAE,kBAAuB,GAAG,MAAM,CAE7F;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CACvC,MAAM,EAAE,KAAK,EAAE,EACf,QAAQ,EAAE;IACN,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB,EACD,OAAO,EAAE,kBAAkB,GAAG;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB,GACF,MAAM,CA8GR"}
@@ -16,11 +16,8 @@ import { blockRegistry } from '../../registry/BlockRegistry';
16
16
  */
17
17
  export function renderBlockToEmail(block, context = {}) {
18
18
  const definition = blockRegistry.get(block.type);
19
- if (!definition) {
20
- return `<p style="color: #666; font-size: 14px; padding: 10px;">Unknown block type: ${block.type}</p>`;
21
- }
22
- // Check if block has email-specific renderer (preferred)
23
- if (definition.components.Email) {
19
+ // If block is registered and has Email component, use it
20
+ if (definition?.components?.Email) {
24
21
  const childBlocks = block.children && Array.isArray(block.children) && block.children.length > 0
25
22
  ? (typeof block.children[0] === 'object' ? block.children : [])
26
23
  : [];
@@ -31,8 +28,7 @@ export function renderBlockToEmail(block, context = {}) {
31
28
  renderChild: (childBlock) => renderBlockToEmail(childBlock, context),
32
29
  });
33
30
  }
34
- // Fallback to converting Preview component output to email HTML
35
- // For now, we'll create basic email-safe HTML based on block type
31
+ // Fallback: render using built-in email-safe HTML (handles all standard block types)
36
32
  return renderBlockByType(block, context);
37
33
  }
38
34
  /**
@@ -0,0 +1,16 @@
1
+ /**
2
+ * i18n Utility
3
+ * Simple translation loader for the newsletter plugin
4
+ */
5
+ export declare function getTranslations(locale: string): any;
6
+ export declare function getTestEmailTranslations(language: string): {
7
+ title: string;
8
+ greeting: string;
9
+ description: string;
10
+ successTitle: string;
11
+ successMessage: string;
12
+ recipientLabel: string;
13
+ footerText: string;
14
+ ignoreText: string;
15
+ };
16
+ //# sourceMappingURL=i18n.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../../src/lib/i18n.ts"],"names":[],"mappings":"AAAA;;;GAGG;AA4CH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,GAAG,CASnD;AAED,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,GAAG;IACxD,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACtB,CAcA"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * i18n Utility
3
+ * Simple translation loader for the newsletter plugin
4
+ */
5
+ import fs from 'fs';
6
+ import path from 'path';
7
+ function getLocalesDir() {
8
+ const possiblePaths = [
9
+ path.join(process.cwd(), 'node_modules', '@jhits', 'plugin-newsletter', 'data', 'locales'),
10
+ path.join(__dirname, '..', 'data', 'locales'),
11
+ path.join(__dirname, '..', '..', 'data', 'locales'),
12
+ ];
13
+ for (const localesPath of possiblePaths) {
14
+ if (fs.existsSync(localesPath)) {
15
+ return localesPath;
16
+ }
17
+ }
18
+ return possiblePaths[0];
19
+ }
20
+ const localesDir = getLocalesDir();
21
+ const cache = {};
22
+ function loadLocale(locale) {
23
+ if (cache[locale]) {
24
+ return cache[locale];
25
+ }
26
+ try {
27
+ const localePath = path.join(localesDir, locale, 'common.json');
28
+ if (fs.existsSync(localePath)) {
29
+ const content = fs.readFileSync(localePath, 'utf-8');
30
+ cache[locale] = JSON.parse(content);
31
+ return cache[locale];
32
+ }
33
+ }
34
+ catch (error) {
35
+ console.error(`[i18n] Failed to load locale ${locale}:`, error);
36
+ }
37
+ return null;
38
+ }
39
+ export function getTranslations(locale) {
40
+ const translations = loadLocale(locale);
41
+ if (translations) {
42
+ return translations;
43
+ }
44
+ const fallback = loadLocale('en');
45
+ return fallback || {};
46
+ }
47
+ export function getTestEmailTranslations(language) {
48
+ const translations = getTranslations(language);
49
+ const enTranslations = getTranslations('en');
50
+ return translations.testEmail || enTranslations.testEmail || {
51
+ title: 'SMTP Configuration Verified',
52
+ greeting: 'Hello,',
53
+ description: 'This is a test email to verify that your SMTP configuration is working correctly.',
54
+ successTitle: 'Success!',
55
+ successMessage: 'Your SMTP settings are configured correctly.',
56
+ recipientLabel: 'Email sent to:',
57
+ footerText: 'This is an automated test email from',
58
+ ignoreText: 'If you did not expect to receive this email, you can safely ignore it.',
59
+ };
60
+ }
@@ -29,13 +29,15 @@ export interface EditorProviderProps {
29
29
  /** Background color for dark mode (optional) */
30
30
  dark?: string;
31
31
  };
32
+ /** If true, this editor is for the welcome email */
33
+ isWelcomeEmail?: boolean;
32
34
  }
33
35
  /**
34
36
  * Editor Provider
35
37
  * Provides editor state and actions to child components
36
38
  * Automatically registers client-provided blocks on mount
37
39
  */
38
- export declare function EditorProvider({ children, initialState, onSave, customBlocks, darkMode, backgroundColors }: EditorProviderProps): import("react/jsx-runtime").JSX.Element;
40
+ export declare function EditorProvider({ children, initialState, onSave, customBlocks, darkMode, backgroundColors, isWelcomeEmail }: EditorProviderProps): import("react/jsx-runtime").JSX.Element;
39
41
  /**
40
42
  * Hook to access editor context
41
43
  * @throws Error if used outside EditorProvider
@@ -1 +1 @@
1
- {"version":3,"file":"EditorContext.d.ts","sourceRoot":"","sources":["../../src/state/EditorContext.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,KAAmG,MAAM,OAAO,CAAC;AAExH,OAAO,EAAE,WAAW,EAAoC,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAG5F,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAMvD;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAChC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,+BAA+B;IAC/B,YAAY,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;IACpC,sCAAsC;IACtC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C;;;OAGG;IACH,YAAY,CAAC,EAAE,qBAAqB,EAAE,CAAC;IACvC,qEAAqE;IACrE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uCAAuC;IACvC,gBAAgB,CAAC,EAAE;QACf,iDAAiD;QACjD,KAAK,EAAE,MAAM,CAAC;QACd,gDAAgD;QAChD,IAAI,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;CACL;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,EAC3B,QAAQ,EACR,YAAY,EACZ,MAAM,EACN,YAAiB,EACjB,QAAe,EACf,gBAAgB,EACnB,EAAE,mBAAmB,2CAkNrB;AAED;;;GAGG;AACH,wBAAgB,SAAS,IAAI,kBAAkB,CAM9C"}
1
+ {"version":3,"file":"EditorContext.d.ts","sourceRoot":"","sources":["../../src/state/EditorContext.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,KAAmG,MAAM,OAAO,CAAC;AAExH,OAAO,EAAE,WAAW,EAAoC,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAG5F,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAMvD;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAChC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,+BAA+B;IAC/B,YAAY,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;IACpC,sCAAsC;IACtC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C;;;OAGG;IACH,YAAY,CAAC,EAAE,qBAAqB,EAAE,CAAC;IACvC,qEAAqE;IACrE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uCAAuC;IACvC,gBAAgB,CAAC,EAAE;QACf,iDAAiD;QACjD,KAAK,EAAE,MAAM,CAAC;QACd,gDAAgD;QAChD,IAAI,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,oDAAoD;IACpD,cAAc,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,EAC3B,QAAQ,EACR,YAAY,EACZ,MAAM,EACN,YAAiB,EACjB,QAAe,EACf,gBAAgB,EAChB,cAAc,EACjB,EAAE,mBAAmB,2CA8MrB;AAED;;;GAGG;AACH,wBAAgB,SAAS,IAAI,kBAAkB,CAM9C"}
@@ -16,7 +16,7 @@ const EditorContext = createContext(null);
16
16
  * Provides editor state and actions to child components
17
17
  * Automatically registers client-provided blocks on mount
18
18
  */
19
- export function EditorProvider({ children, initialState, onSave, customBlocks = [], darkMode = true, backgroundColors }) {
19
+ export function EditorProvider({ children, initialState, onSave, customBlocks = [], darkMode = true, backgroundColors, isWelcomeEmail }) {
20
20
  // Register client blocks on mount
21
21
  useEffect(() => {
22
22
  if (customBlocks && customBlocks.length > 0) {
@@ -116,12 +116,8 @@ export function EditorProvider({ children, initialState, onSave, customBlocks =
116
116
  const resetEditor = useCallback(() => {
117
117
  dispatch({ type: 'RESET_EDITOR' });
118
118
  }, []);
119
- // Helper: Save
120
- // Uses stateRef to always get the latest state, avoiding stale closure issues
121
119
  const save = useCallback(async () => {
122
120
  if (onSave) {
123
- // Use stateRef.current to get the absolute latest state
124
- // This ensures we don't have stale closure issues with React state updates
125
121
  await onSave(stateRef.current);
126
122
  dispatch({ type: 'MARK_CLEAN' });
127
123
  }
@@ -453,11 +453,11 @@ export function editorReducer(state, action) {
453
453
  const newsletter = action.payload;
454
454
  return {
455
455
  ...state,
456
- blocks: newsletter.blocks,
457
- title: newsletter.title,
458
- slug: newsletter.slug,
459
- metadata: newsletter.metadata,
460
- status: newsletter.publication.status,
456
+ blocks: newsletter.blocks || [],
457
+ title: newsletter.title || 'Untitled',
458
+ slug: newsletter.slug || '',
459
+ metadata: newsletter.metadata || { subject: '', previewText: '' },
460
+ status: newsletter.publication?.status || 'draft',
461
461
  newsletterId: newsletter.id,
462
462
  isDirty: false,
463
463
  selectedBlockId: null,
@@ -94,6 +94,7 @@ export interface NewsletterListItem {
94
94
  authorId?: string;
95
95
  updatedAt: string;
96
96
  recipientCount?: number;
97
+ hidden?: boolean;
97
98
  }
98
99
  /**
99
100
  * Newsletter Filter Options
@@ -1 +1 @@
1
- {"version":3,"file":"newsletter.d.ts","sourceRoot":"","sources":["../../src/types/newsletter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"newsletter.d.ts","sourceRoot":"","sources":["../../src/types/newsletter.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  export interface CanvasEditorViewProps {
2
- newsletterSlug?: string;
2
+ newsletterId?: string;
3
3
  siteId: string;
4
4
  locale: string;
5
5
  /** Enable dark mode for content area and wrappers (default: true) */
@@ -9,6 +9,8 @@ export interface CanvasEditorViewProps {
9
9
  light: string;
10
10
  dark?: string;
11
11
  };
12
+ /** If true, this editor is for the welcome email */
13
+ isWelcomeEmail?: boolean;
12
14
  }
13
- export declare function CanvasEditorView({ newsletterSlug, darkMode, backgroundColors: propsBackgroundColors, siteId, locale }: CanvasEditorViewProps): import("react/jsx-runtime").JSX.Element;
15
+ export declare function CanvasEditorView({ newsletterId, darkMode, backgroundColors: propsBackgroundColors, siteId, locale, isWelcomeEmail }: CanvasEditorViewProps): import("react/jsx-runtime").JSX.Element;
14
16
  //# sourceMappingURL=CanvasEditorView.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"CanvasEditorView.d.ts","sourceRoot":"","sources":["../../../src/views/CanvasEditor/CanvasEditorView.tsx"],"names":[],"mappings":"AAeA,MAAM,WAAW,qBAAqB;IAClC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,qEAAqE;IACrE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uCAAuC;IACvC,gBAAgB,CAAC,EAAE;QACf,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;CACL;AAED,wBAAgB,gBAAgB,CAAC,EAAE,cAAc,EAAE,QAAQ,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,qBAAqB,2CA4N5I"}
1
+ {"version":3,"file":"CanvasEditorView.d.ts","sourceRoot":"","sources":["../../../src/views/CanvasEditor/CanvasEditorView.tsx"],"names":[],"mappings":"AAeA,MAAM,WAAW,qBAAqB;IAClC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,qEAAqE;IACrE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uCAAuC;IACvC,gBAAgB,CAAC,EAAE;QACf,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,oDAAoD;IACpD,cAAc,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,wBAAgB,gBAAgB,CAAC,EAAE,YAAY,EAAE,QAAQ,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,EAAE,qBAAqB,2CAwT1J"}
@@ -10,7 +10,7 @@ import { SlashCommandMenu } from './components/SlashCommandMenu';
10
10
  import { useSlashCommand } from './hooks/useSlashCommand';
11
11
  import { useNewsletterLoader, useRegisteredBlocks, useKeyboardShortcuts } from './hooks';
12
12
  import { blockRegistry } from '../../registry';
13
- export function CanvasEditorView({ newsletterSlug, darkMode, backgroundColors: propsBackgroundColors, siteId, locale }) {
13
+ export function CanvasEditorView({ newsletterId, darkMode, backgroundColors: propsBackgroundColors, siteId, locale, isWelcomeEmail }) {
14
14
  const { state, helpers, dispatch, darkMode: contextDarkMode, backgroundColors: contextBackgroundColors, canUndo, canRedo } = useEditor();
15
15
  const effectiveDarkMode = darkMode !== undefined ? darkMode : contextDarkMode;
16
16
  const effectiveBackgroundColors = propsBackgroundColors || contextBackgroundColors;
@@ -18,21 +18,85 @@ export function CanvasEditorView({ newsletterSlug, darkMode, backgroundColors: p
18
18
  const [isPreviewMode, setIsPreviewMode] = useState(false);
19
19
  const [isSaving, setIsSaving] = useState(false);
20
20
  const [saveError, setSaveError] = useState(null);
21
+ // Primary language from SMTP settings (used for welcome email)
22
+ const [primaryLanguage, setPrimaryLanguage] = useState(locale || 'en');
23
+ const [isLoadingLanguage, setIsLoadingLanguage] = useState(true);
24
+ const [availableLanguages, setAvailableLanguages] = useState([locale || 'en']);
25
+ const [currentLanguage, setCurrentLanguage] = useState(locale || 'en');
21
26
  // Get registered blocks
22
27
  const registeredBlocks = useRegisteredBlocks();
23
- // Newsletter loading
24
- const { isLoadingNewsletter } = useNewsletterLoader(newsletterSlug, state.newsletterId, (newsletter) => {
28
+ // Newsletter loading - use current language for welcome email (wait until language settings are loaded)
29
+ const { isLoadingNewsletter } = useNewsletterLoader(newsletterId, state.newsletterId, (newsletter) => {
25
30
  helpers.loadNewsletter(newsletter);
31
+ // Set current language from loaded newsletter metadata
32
+ if (newsletter.metadata?.lang && !isWelcomeEmail) {
33
+ setCurrentLanguage(newsletter.metadata.lang);
34
+ }
26
35
  setTimeout(() => {
27
36
  dispatch({ type: 'MARK_CLEAN' });
28
37
  }, 0);
29
- });
38
+ }, isWelcomeEmail, !isLoadingLanguage ? currentLanguage : undefined);
39
+ // Fetch primary language and available languages from SMTP/welcome-email settings
40
+ useEffect(() => {
41
+ const fetchLanguageSettings = async () => {
42
+ try {
43
+ const [smtpResponse, welcomeResponse] = await Promise.all([
44
+ fetch('/api/plugin-newsletter/smtp'),
45
+ fetch('/api/plugin-newsletter/welcome-email/status')
46
+ ]);
47
+ const smtpData = await smtpResponse.json();
48
+ const welcomeData = await welcomeResponse.json();
49
+ const primary = smtpData.primaryLanguage || 'en';
50
+ const available = welcomeData.availableLanguages || [primary];
51
+ if (!available.includes(primary)) {
52
+ available.unshift(primary);
53
+ }
54
+ setPrimaryLanguage(primary);
55
+ setAvailableLanguages(available);
56
+ setCurrentLanguage(primary);
57
+ }
58
+ catch (error) {
59
+ console.error('Failed to fetch language settings:', error);
60
+ }
61
+ finally {
62
+ setIsLoadingLanguage(false);
63
+ }
64
+ };
65
+ fetchLanguageSettings();
66
+ }, []);
67
+ // Handle language change for welcome email
68
+ const handleLanguageChange = async (newLanguage) => {
69
+ // Save current content first if dirty
70
+ if (state.isDirty) {
71
+ const confirmed = window.confirm('You have unsaved changes. Do you want to save them first?');
72
+ if (confirmed) {
73
+ await handleSave();
74
+ }
75
+ }
76
+ setCurrentLanguage(newLanguage);
77
+ // Reload with new language
78
+ const response = await fetch(`/api/plugin-newsletter/welcome-email?language=${newLanguage}`);
79
+ if (response.ok) {
80
+ const newsletter = await response.json();
81
+ helpers.loadNewsletter(newsletter);
82
+ dispatch({ type: 'MARK_CLEAN' });
83
+ }
84
+ };
85
+ // Handle adding a new language
86
+ const handleAddLanguage = async (newLanguage) => {
87
+ if (availableLanguages.includes(newLanguage))
88
+ return;
89
+ // Add the new language to the list
90
+ setAvailableLanguages([...availableLanguages, newLanguage]);
91
+ // Switch to the new language (it will copy from primary language)
92
+ await handleLanguageChange(newLanguage);
93
+ };
30
94
  // Ensure at least one paragraph block exists when creating new newsletter
31
95
  useEffect(() => {
32
- if (!newsletterSlug && state.blocks.length === 0 && !isLoadingNewsletter) {
96
+ if (!newsletterId && !isWelcomeEmail && state.blocks.length === 0 && !isLoadingNewsletter) {
33
97
  helpers.addBlock('paragraph', 0, undefined);
34
98
  }
35
- }, [newsletterSlug, state.blocks.length, isLoadingNewsletter, helpers]);
99
+ }, [newsletterId, state.blocks.length, isLoadingNewsletter, helpers, isWelcomeEmail]);
36
100
  // Track if we just loaded a newsletter to prevent marking as dirty during cleanup
37
101
  const justLoadedRef = useRef(false);
38
102
  const previousIsLoadingRef = useRef(false);
@@ -48,7 +112,6 @@ export function CanvasEditorView({ newsletterSlug, darkMode, backgroundColors: p
48
112
  requestAnimationFrame(() => {
49
113
  requestAnimationFrame(() => {
50
114
  loadingCleanupTimerRef.current = setTimeout(() => {
51
- console.log('[CanvasEditorView] Newsletter loading complete - ensuring clean state');
52
115
  dispatch({ type: 'MARK_CLEAN' });
53
116
  justLoadedRef.current = false;
54
117
  loadingCleanupTimerRef.current = null;
@@ -124,16 +187,16 @@ export function CanvasEditorView({ newsletterSlug, darkMode, backgroundColors: p
124
187
  if (isLoadingNewsletter) {
125
188
  return (_jsx("div", { className: "h-full w-full bg-dashboard-card text-dashboard-text flex items-center justify-center", children: _jsxs("div", { className: "text-center", children: [_jsx("div", { className: "w-8 h-8 border-4 border-primary/20 border-t-primary rounded-full animate-spin mx-auto mb-4" }), _jsx("p", { className: "text-sm text-neutral-500 dark:text-neutral-400", children: "Loading newsletter..." })] }) }));
126
189
  }
127
- return (_jsx("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", children: _jsxs("main", { className: "flex flex-1 flex-col relative min-h-0", children: [_jsx(ErrorBanner, { error: saveError, onDismiss: () => setSaveError(null) }), _jsx(EditorHeader, { isPreviewMode: isPreviewMode, onPreviewToggle: () => setIsPreviewMode(!isPreviewMode), isSidebarOpen: isSidebarOpen, onSidebarToggle: () => setSidebarOpen(!isSidebarOpen), isSaving: isSaving, onSave: handleSave, onSaveError: (error) => {
190
+ return (_jsx("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", children: isLoadingLanguage ? (_jsx("div", { className: "h-full w-full flex items-center justify-center", children: _jsxs("div", { className: "text-center", children: [_jsx("div", { className: "w-8 h-8 border-2 border-primary border-t-transparent rounded-full animate-spin mx-auto mb-4" }), _jsx("p", { className: "text-sm text-dashboard-text-secondary", children: "Loading..." })] }) })) : (_jsxs("main", { className: "flex flex-1 flex-col relative min-h-0", children: [_jsx(ErrorBanner, { error: saveError, onDismiss: () => setSaveError(null) }), _jsx(EditorHeader, { isPreviewMode: isPreviewMode, onPreviewToggle: () => setIsPreviewMode(!isPreviewMode), isSidebarOpen: isSidebarOpen, onSidebarToggle: () => setSidebarOpen(!isSidebarOpen), isSaving: isSaving, onSave: handleSave, onSaveError: (error) => {
128
191
  if (error) {
129
192
  setSaveError(error);
130
193
  }
131
194
  else {
132
195
  setSaveError(null);
133
196
  }
134
- }, isDirty: state.isDirty }), _jsxs("div", { className: "flex flex-1 relative overflow-hidden min-h-0 flex-nowrap", children: [_jsx(EditorCanvas, { isPreviewMode: isPreviewMode, contentBlocks: state.blocks, title: state.title, siteId: siteId, locale: locale, darkMode: effectiveDarkMode, backgroundColors: effectiveBackgroundColors, metadata: state.metadata, onTitleChange: (title) => dispatch({ type: 'SET_TITLE', payload: title }), onMetadataChange: (metadata) => dispatch({ type: 'SET_METADATA', payload: metadata }), onBlockAdd: (type, index, containerId) => helpers.addBlock(type, index, containerId), onBlockUpdate: (id, data) => helpers.updateBlock(id, data), onBlockDelete: (id) => helpers.deleteBlock(id), onBlockMove: (id, newIndex, containerId) => helpers.moveBlock(id, newIndex, containerId), slashCommand: slashCommand }), !isPreviewMode && (_jsx("aside", { 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'}`, children: _jsx(EditorSidebar, { slug: state.slug, metadata: state.metadata, status: state.status, onMetadataUpdate: (metadata) => dispatch({ type: 'SET_METADATA', payload: metadata }) }) }))] }), slashCommand.isOpen && slashCommand.position && (_jsx(SlashCommandMenu, { blocks: slashCommand.filteredBlocks, query: slashCommand.query, selectedIndex: slashCommand.selectedIndex, onSelect: (replaceBlockId) => {
197
+ }, isDirty: state.isDirty, isWelcomeEmail: isWelcomeEmail, languages: availableLanguages, currentLanguage: currentLanguage, onLanguageChange: handleLanguageChange, onAddLanguage: handleAddLanguage }), _jsxs("div", { className: "flex flex-1 relative overflow-hidden min-h-0 flex-nowrap", children: [_jsx(EditorCanvas, { isPreviewMode: isPreviewMode, contentBlocks: state.blocks, title: state.title, siteId: siteId, locale: locale, darkMode: effectiveDarkMode, backgroundColors: effectiveBackgroundColors, metadata: state.metadata, onTitleChange: (title) => dispatch({ type: 'SET_TITLE', payload: title }), onMetadataChange: (metadata) => dispatch({ type: 'SET_METADATA', payload: metadata }), onBlockAdd: (type, index, containerId) => helpers.addBlock(type, index, containerId), onBlockUpdate: (id, data) => helpers.updateBlock(id, data), onBlockDelete: (id) => helpers.deleteBlock(id), onBlockMove: (id, newIndex, containerId) => helpers.moveBlock(id, newIndex, containerId), slashCommand: slashCommand }), !isPreviewMode && (_jsx("aside", { 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'}`, children: _jsx(EditorSidebar, { metadata: state.metadata, status: state.status, onMetadataUpdate: (metadata) => dispatch({ type: 'SET_METADATA', payload: metadata }), defaultLanguage: primaryLanguage, isWelcomeEmail: isWelcomeEmail, languages: availableLanguages, currentLanguage: currentLanguage, onLanguageChange: handleLanguageChange }) }))] }), slashCommand.isOpen && slashCommand.position && (_jsx(SlashCommandMenu, { blocks: slashCommand.filteredBlocks, query: slashCommand.query, selectedIndex: slashCommand.selectedIndex, onSelect: (replaceBlockId) => {
135
198
  // Use the replaceBlockId passed from the menu (which comes from state)
136
199
  // This ensures we use the correct block ID that was set when the menu opened
137
200
  slashCommand.selectCurrent(replaceBlockId);
138
- }, position: slashCommand.position, onClose: slashCommand.closeMenu, replaceBlockId: slashCommand.replaceBlockId }))] }) }));
201
+ }, position: slashCommand.position, onClose: slashCommand.closeMenu, replaceBlockId: slashCommand.replaceBlockId }))] })) }));
139
202
  }
@@ -7,6 +7,11 @@ export interface EditorHeaderProps {
7
7
  onSave: () => Promise<void>;
8
8
  onSaveError: (error: string | null) => void;
9
9
  isDirty?: boolean;
10
+ isWelcomeEmail?: boolean;
11
+ languages?: string[];
12
+ currentLanguage?: string;
13
+ onLanguageChange?: (language: string) => void;
14
+ onAddLanguage?: (language: string) => void;
10
15
  }
11
- export declare function EditorHeader({ isPreviewMode, onPreviewToggle, isSidebarOpen, onSidebarToggle, isSaving, onSave, onSaveError, isDirty, }: EditorHeaderProps): import("react/jsx-runtime").JSX.Element;
16
+ export declare function EditorHeader({ isPreviewMode, onPreviewToggle, isSidebarOpen, onSidebarToggle, isSaving, onSave, onSaveError, isDirty, isWelcomeEmail, languages, currentLanguage, onLanguageChange, onAddLanguage, }: EditorHeaderProps): import("react/jsx-runtime").JSX.Element;
12
17
  //# sourceMappingURL=EditorHeader.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"EditorHeader.d.ts","sourceRoot":"","sources":["../../../src/views/CanvasEditor/EditorHeader.tsx"],"names":[],"mappings":"AAMA,MAAM,WAAW,iBAAiB;IAC9B,aAAa,EAAE,OAAO,CAAC;IACvB,eAAe,EAAE,MAAM,IAAI,CAAC;IAC5B,aAAa,EAAE,OAAO,CAAC;IACvB,eAAe,EAAE,MAAM,IAAI,CAAC;IAC5B,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IAC5C,OAAO,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,wBAAgB,YAAY,CAAC,EACzB,aAAa,EACb,eAAe,EACf,aAAa,EACb,eAAe,EACf,QAAQ,EACR,MAAM,EACN,WAAW,EACX,OAAe,GAClB,EAAE,iBAAiB,2CAgHnB"}
1
+ {"version":3,"file":"EditorHeader.d.ts","sourceRoot":"","sources":["../../../src/views/CanvasEditor/EditorHeader.tsx"],"names":[],"mappings":"AAqCA,MAAM,WAAW,iBAAiB;IAC9B,aAAa,EAAE,OAAO,CAAC;IACvB,eAAe,EAAE,MAAM,IAAI,CAAC;IAC5B,aAAa,EAAE,OAAO,CAAC;IACvB,eAAe,EAAE,MAAM,IAAI,CAAC;IAC5B,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IAC5C,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9C,aAAa,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9C;AAED,wBAAgB,YAAY,CAAC,EACzB,aAAa,EACb,eAAe,EACf,aAAa,EACb,eAAe,EACf,QAAQ,EACR,MAAM,EACN,WAAW,EACX,OAAe,EACf,cAAsB,EACtB,SAAkB,EAClB,eAAsB,EACtB,gBAAgB,EAChB,aAAa,GAChB,EAAE,iBAAiB,2CA2LnB"}