@jhits/plugin-newsletter 0.0.10 → 0.0.11

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 (57) hide show
  1. package/package.json +3 -2
  2. package/src/api/email-utils.ts +165 -0
  3. package/src/api/handler.ts +28 -0
  4. package/src/api/handlers/index.ts +44 -0
  5. package/src/api/handlers/newsletters.ts +332 -0
  6. package/src/api/handlers/send-newsletter.ts +288 -0
  7. package/src/api/handlers/settings.ts +403 -0
  8. package/src/api/handlers/subscribers.ts +152 -0
  9. package/src/api/handlers/upload.ts +47 -0
  10. package/src/api/handlers/welcome-email.ts +210 -0
  11. package/src/api/router.ts +166 -0
  12. package/src/index.server.ts +12 -0
  13. package/src/index.tsx +353 -0
  14. package/src/index.tsx.patch +98 -0
  15. package/src/init.tsx +72 -0
  16. package/src/lib/blocks/BlockRenderer.tsx +125 -0
  17. package/src/lib/email/EmailRenderer.tsx +420 -0
  18. package/src/lib/email/index.ts +6 -0
  19. package/src/lib/i18n.ts +82 -0
  20. package/src/lib/mappers/apiMapper.ts +57 -0
  21. package/src/lib/utils/blockHelpers.ts +71 -0
  22. package/src/lib/utils/slugify.ts +43 -0
  23. package/src/registry/BlockRegistry.ts +53 -0
  24. package/src/registry/index.ts +5 -0
  25. package/src/state/EditorContext.tsx +278 -0
  26. package/src/state/index.ts +10 -0
  27. package/src/state/reducer.ts +561 -0
  28. package/src/state/types.ts +154 -0
  29. package/src/types/block.ts +275 -0
  30. package/src/types/newsletter.ts +152 -0
  31. package/src/types/registry.ts +14 -0
  32. package/src/views/CanvasEditor/BlockWrapper.tsx +143 -0
  33. package/src/views/CanvasEditor/CanvasEditorView.tsx +343 -0
  34. package/src/views/CanvasEditor/EditorBody.tsx +95 -0
  35. package/src/views/CanvasEditor/EditorHeader.tsx +255 -0
  36. package/src/views/CanvasEditor/components/CustomBlockItem.tsx +83 -0
  37. package/src/views/CanvasEditor/components/EditorCanvas.tsx +674 -0
  38. package/src/views/CanvasEditor/components/EditorLibrary.tsx +120 -0
  39. package/src/views/CanvasEditor/components/EditorSidebar.tsx +139 -0
  40. package/src/views/CanvasEditor/components/ErrorBanner.tsx +31 -0
  41. package/src/views/CanvasEditor/components/LibraryItem.tsx +71 -0
  42. package/src/views/CanvasEditor/components/SlashCommandDetector.tsx +196 -0
  43. package/src/views/CanvasEditor/components/SlashCommandMenu.tsx +131 -0
  44. package/src/views/CanvasEditor/components/index.ts +16 -0
  45. package/src/views/CanvasEditor/hooks/index.ts +7 -0
  46. package/src/views/CanvasEditor/hooks/useKeyboardShortcuts.ts +136 -0
  47. package/src/views/CanvasEditor/hooks/useNewsletterLoader.ts +73 -0
  48. package/src/views/CanvasEditor/hooks/useRegisteredBlocks.ts +54 -0
  49. package/src/views/CanvasEditor/hooks/useSlashCommand.ts +106 -0
  50. package/src/views/CanvasEditor/index.ts +12 -0
  51. package/src/views/NewsletterEditor.tsx +42 -0
  52. package/src/views/NewsletterManager.tsx +483 -0
  53. package/src/views/SettingsView.tsx +216 -0
  54. package/src/views/SubscribersView.tsx +269 -0
  55. package/src/views/components/SendNewsletterModal.tsx +322 -0
  56. package/src/views/components/SmtpSettingsModal.tsx +433 -0
  57. package/src/views/components/TestEmailModal.tsx +268 -0
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Subscribers API Handler
3
+ */
4
+
5
+ 'use server';
6
+
7
+ import { NextRequest, NextResponse } from 'next/server';
8
+ import { NewsletterApiConfig } from '../../types/newsletter';
9
+ import { sendWelcomeEmail } from '../email-utils';
10
+
11
+ export async function GET_SUBSCRIBERS(
12
+ req: NextRequest,
13
+ config: NewsletterApiConfig
14
+ ): Promise<NextResponse> {
15
+ try {
16
+ const userId = await config.getUserId?.(req);
17
+ if (!userId) {
18
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
19
+ }
20
+
21
+ const dbConnection = await config.getDb();
22
+ const db = dbConnection.db();
23
+ const subscribers = db.collection('subscribers');
24
+
25
+ const subscriberList = await subscribers
26
+ .find({})
27
+ .sort({ subscribedAt: -1 })
28
+ .toArray();
29
+
30
+ return NextResponse.json(subscriberList);
31
+ } catch (error: any) {
32
+ console.error('[NewsletterAPI] GET_SUBSCRIBERS error:', error);
33
+ return NextResponse.json(
34
+ { error: 'Failed to fetch subscribers', detail: error.message },
35
+ { status: 500 }
36
+ );
37
+ }
38
+ }
39
+
40
+ export async function POST_SUBSCRIBE(
41
+ req: NextRequest,
42
+ config: NewsletterApiConfig
43
+ ): Promise<NextResponse> {
44
+ try {
45
+ const body = await req.json();
46
+ const { email, language } = body;
47
+
48
+ if (!email || !email.includes('@')) {
49
+ return NextResponse.json(
50
+ { error: 'Invalid email address' },
51
+ { status: 400 }
52
+ );
53
+ }
54
+
55
+ const dbConnection = await config.getDb();
56
+ const db = dbConnection.db();
57
+ const subscribers = db.collection('subscribers');
58
+
59
+ const existing = await subscribers.findOne({ email: email.toLowerCase() });
60
+ if (existing) {
61
+ return NextResponse.json(
62
+ { error: 'You are already subscribed!' },
63
+ { status: 409 }
64
+ );
65
+ }
66
+
67
+ await subscribers.insertOne({
68
+ email: email.toLowerCase(),
69
+ language: language || 'en',
70
+ subscribedAt: new Date(),
71
+ status: 'active',
72
+ });
73
+
74
+ const headers = await req.headers;
75
+ const host = headers.get('host') || undefined;
76
+ await sendWelcomeEmail(config, email, language || 'en', host);
77
+
78
+ return NextResponse.json({ message: 'Successfully subscribed' }, { status: 201 });
79
+ } catch (error: any) {
80
+ console.error('[NewsletterAPI] POST_SUBSCRIBE error:', error);
81
+ return NextResponse.json(
82
+ { error: 'Failed to subscribe', detail: error.message },
83
+ { status: 500 }
84
+ );
85
+ }
86
+ }
87
+
88
+ export async function GET_SUBSCRIBER(
89
+ req: NextRequest,
90
+ email: string,
91
+ config: NewsletterApiConfig
92
+ ): Promise<NextResponse> {
93
+ try {
94
+ const userId = await config.getUserId?.(req);
95
+ if (!userId) {
96
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
97
+ }
98
+
99
+ const dbConnection = await config.getDb();
100
+ const db = dbConnection.db();
101
+ const subscribers = db.collection('subscribers');
102
+
103
+ const subscriber = await subscribers.findOne({ email: email.toLowerCase() });
104
+ if (!subscriber) {
105
+ return NextResponse.json(
106
+ { error: 'Subscriber not found' },
107
+ { status: 404 }
108
+ );
109
+ }
110
+
111
+ return NextResponse.json(subscriber);
112
+ } catch (error: any) {
113
+ console.error('[NewsletterAPI] GET_SUBSCRIBER error:', error);
114
+ return NextResponse.json(
115
+ { error: 'Failed to fetch subscriber', detail: error.message },
116
+ { status: 500 }
117
+ );
118
+ }
119
+ }
120
+
121
+ export async function DELETE_SUBSCRIBER(
122
+ req: NextRequest,
123
+ email: string,
124
+ config: NewsletterApiConfig
125
+ ): Promise<NextResponse> {
126
+ try {
127
+ const userId = await config.getUserId?.(req);
128
+ if (!userId) {
129
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
130
+ }
131
+
132
+ const dbConnection = await config.getDb();
133
+ const db = dbConnection.db();
134
+ const subscribers = db.collection('subscribers');
135
+
136
+ const result = await subscribers.deleteOne({ email: email.toLowerCase() });
137
+ if (result.deletedCount === 0) {
138
+ return NextResponse.json(
139
+ { error: 'Subscriber not found' },
140
+ { status: 404 }
141
+ );
142
+ }
143
+
144
+ return NextResponse.json({ message: 'Subscriber successfully removed' });
145
+ } catch (error: any) {
146
+ console.error('[NewsletterAPI] DELETE_SUBSCRIBER error:', error);
147
+ return NextResponse.json(
148
+ { error: 'Failed to delete subscriber', detail: error.message },
149
+ { status: 500 }
150
+ );
151
+ }
152
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Logo Upload Handler
3
+ */
4
+
5
+ 'use server';
6
+
7
+ import { NextRequest, NextResponse } from 'next/server';
8
+ import { NewsletterApiConfig } from '../../types/newsletter';
9
+
10
+ export async function POST_LOGO_UPLOAD(
11
+ req: NextRequest,
12
+ config: NewsletterApiConfig
13
+ ): Promise<NextResponse> {
14
+ try {
15
+ const userId = await config.getUserId?.(req);
16
+ if (!userId) {
17
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
18
+ }
19
+
20
+ const formData = await req.formData();
21
+ const file = formData.get('file') as File | null;
22
+
23
+ if (!file) {
24
+ return NextResponse.json({ error: 'No file provided' }, { status: 400 });
25
+ }
26
+
27
+ const allowedTypes = ['image/png', 'image/jpeg'];
28
+ if (!allowedTypes.includes(file.type)) {
29
+ return NextResponse.json({ error: 'Invalid file type. Only PNG and JPEG are allowed.' }, { status: 400 });
30
+ }
31
+
32
+ const bytes = await file.arrayBuffer();
33
+ const buffer = Buffer.from(bytes);
34
+ const base64 = buffer.toString('base64');
35
+ const mimeType = file.type;
36
+
37
+ const dataUrl = `data:${mimeType};base64,${base64}`;
38
+
39
+ return NextResponse.json({ url: dataUrl, success: true });
40
+ } catch (error: any) {
41
+ console.error('[NewsletterAPI] POST_LOGO_UPLOAD error:', error);
42
+ return NextResponse.json(
43
+ { error: 'Failed to upload logo', detail: error.message },
44
+ { status: 500 }
45
+ );
46
+ }
47
+ }
@@ -0,0 +1,210 @@
1
+ /**
2
+ * Welcome Email API Handler
3
+ */
4
+
5
+ 'use server';
6
+
7
+ import { NextRequest, NextResponse } from 'next/server';
8
+ import { NewsletterApiConfig, NewsletterMetadata } from '../../types/newsletter';
9
+
10
+ interface LanguageContent {
11
+ blocks: any[];
12
+ metadata: NewsletterMetadata;
13
+ }
14
+
15
+ interface WelcomeEmailLanguages {
16
+ en?: LanguageContent;
17
+ nl?: LanguageContent;
18
+ sv?: LanguageContent;
19
+ }
20
+
21
+ export async function GET_WELCOME_EMAIL_STATUS(
22
+ req: NextRequest,
23
+ config: NewsletterApiConfig
24
+ ): Promise<NextResponse> {
25
+ try {
26
+ const userId = await config.getUserId?.(req);
27
+ if (!userId) {
28
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
29
+ }
30
+
31
+ const dbConnection = await config.getDb();
32
+ const db = dbConnection.db();
33
+ const newsletters = db.collection('newsletters');
34
+ const settings = db.collection('settings');
35
+
36
+ const welcomeEmail = await newsletters.findOne({
37
+ type: 'welcome_email',
38
+ id: 'welcome_automation'
39
+ });
40
+
41
+ const smtpConfig = await settings.findOne({ key: 'smtp' });
42
+ const primaryLanguage = smtpConfig?.primaryLanguage || 'en';
43
+
44
+ const languages = (welcomeEmail?.languages as WelcomeEmailLanguages) || {};
45
+ const availableLanguages = Object.keys(languages).filter(
46
+ lang => languages[lang as keyof WelcomeEmailLanguages]?.blocks?.length
47
+ );
48
+
49
+ return NextResponse.json({
50
+ configured: availableLanguages.length > 0,
51
+ lastUpdated: welcomeEmail?.updatedAt || null,
52
+ availableLanguages,
53
+ primaryLanguage,
54
+ });
55
+ } catch (error: any) {
56
+ console.error('[NewsletterAPI] GET_WELCOME_EMAIL_STATUS error:', error);
57
+ return NextResponse.json(
58
+ { error: 'Failed to fetch welcome email status', detail: error.message },
59
+ { status: 500 }
60
+ );
61
+ }
62
+ }
63
+
64
+ export async function GET_WELCOME_EMAIL(
65
+ req: NextRequest,
66
+ config: NewsletterApiConfig
67
+ ): Promise<NextResponse> {
68
+ try {
69
+ const userId = await config.getUserId?.(req);
70
+ if (!userId) {
71
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
72
+
73
+ }
74
+
75
+ const { searchParams } = new URL(req.url);
76
+ const language = searchParams.get('language') || 'en';
77
+
78
+ const dbConnection = await config.getDb();
79
+ const db = dbConnection.db();
80
+ const newsletters = db.collection('newsletters');
81
+ const settings = db.collection('settings');
82
+
83
+ const welcomeEmail = await newsletters.findOne({
84
+ type: 'welcome_email',
85
+ id: 'welcome_automation'
86
+ });
87
+
88
+ const smtpConfig = await settings.findOne({ key: 'smtp' });
89
+ const primaryLanguage = smtpConfig?.primaryLanguage || 'en';
90
+
91
+ const languages = (welcomeEmail?.languages as WelcomeEmailLanguages) || {};
92
+
93
+ // Get primary language content as fallback
94
+ const primaryContent = languages[primaryLanguage as keyof WelcomeEmailLanguages] || languages.en || { blocks: [], metadata: { subject: '', previewText: '' } };
95
+
96
+ // If requested language doesn't exist, copy from primary language
97
+ if (!languages[language as keyof WelcomeEmailLanguages]) {
98
+ return NextResponse.json({
99
+ id: 'welcome_automation',
100
+ type: 'welcome_email',
101
+ title: 'Welcome Email',
102
+ language,
103
+ blocks: primaryContent.blocks,
104
+ metadata: primaryContent.metadata,
105
+ languages,
106
+ isCopy: true,
107
+ copyFrom: primaryLanguage,
108
+ });
109
+ }
110
+
111
+ return NextResponse.json({
112
+ id: 'welcome_automation',
113
+ type: 'welcome_email',
114
+ title: 'Welcome Email',
115
+ language,
116
+ blocks: languages[language as keyof WelcomeEmailLanguages]?.blocks || primaryContent.blocks,
117
+ metadata: languages[language as keyof WelcomeEmailLanguages]?.metadata || primaryContent.metadata,
118
+ languages,
119
+ isCopy: false,
120
+ });
121
+ } catch (error: any) {
122
+ console.error('[NewsletterAPI] GET_WELCOME_EMAIL error:', error);
123
+ return NextResponse.json(
124
+ { error: 'Failed to fetch welcome email', detail: error.message },
125
+ { status: 500 }
126
+ );
127
+ }
128
+ }
129
+
130
+ export async function POST_WELCOME_EMAIL(
131
+ req: NextRequest,
132
+ config: NewsletterApiConfig
133
+ ): Promise<NextResponse> {
134
+ try {
135
+ const userId = await config.getUserId?.(req);
136
+ if (!userId) {
137
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
138
+ }
139
+
140
+ // Get language from query params first (for welcome email saves)
141
+ const { searchParams } = new URL(req.url);
142
+ const queryLanguage = searchParams.get('language');
143
+
144
+ const body = await req.json();
145
+ const { blocks, metadata } = body;
146
+ // Use query param language, fallback to body language, then 'en'
147
+ const language = queryLanguage || body.language || 'en';
148
+
149
+ const dbConnection = await config.getDb();
150
+ const db = dbConnection.db();
151
+ const newsletters = db.collection('newsletters');
152
+
153
+ const existing = await newsletters.findOne({
154
+ type: 'welcome_email',
155
+ id: 'welcome_automation'
156
+ });
157
+
158
+ const existingLanguages = (existing?.languages as WelcomeEmailLanguages) || {};
159
+
160
+ const updatedLanguages: WelcomeEmailLanguages = {
161
+ ...existingLanguages,
162
+ [language]: {
163
+ blocks: blocks || [],
164
+ metadata: metadata || { subject: '', previewText: '' },
165
+ },
166
+ };
167
+
168
+ await newsletters.updateOne(
169
+ { type: 'welcome_email', id: 'welcome_automation' },
170
+ {
171
+ $set: {
172
+ type: 'welcome_email',
173
+ id: 'welcome_automation',
174
+ title: 'Welcome Email',
175
+ languages: updatedLanguages,
176
+ updatedAt: new Date().toISOString(),
177
+ }
178
+ },
179
+ { upsert: true }
180
+ );
181
+
182
+ return NextResponse.json({ success: true, message: 'Welcome email saved successfully' });
183
+ } catch (error: any) {
184
+ console.error('[NewsletterAPI] POST_WELCOME_EMAIL error:', error);
185
+ return NextResponse.json(
186
+ { error: 'Failed to save welcome email', detail: error.message },
187
+ { status: 500 }
188
+ );
189
+ }
190
+ }
191
+
192
+ export async function GET_WELCOME_EMAIL_CONTENT(
193
+ req: NextRequest,
194
+ config: NewsletterApiConfig,
195
+ language: string
196
+ ): Promise<{ blocks: any[]; metadata: NewsletterMetadata }> {
197
+ const dbConnection = await config.getDb();
198
+ const db = dbConnection.db();
199
+ const newsletters = db.collection('newsletters');
200
+
201
+ const welcomeEmail = await newsletters.findOne({
202
+ type: 'welcome_email',
203
+ id: 'welcome_automation'
204
+ });
205
+
206
+ const languages = (welcomeEmail?.languages as WelcomeEmailLanguages) || {};
207
+ const defaultContent = languages.en || { blocks: [], metadata: { subject: '', previewText: '' } };
208
+
209
+ return languages[language as keyof WelcomeEmailLanguages] || defaultContent;
210
+ }
@@ -0,0 +1,166 @@
1
+ /**
2
+ * Newsletter Plugin API Router
3
+ * Routes API requests to appropriate handlers
4
+ */
5
+
6
+ 'use server';
7
+
8
+ import { NextRequest, NextResponse } from 'next/server';
9
+ import { NewsletterApiConfig } from '../types/newsletter';
10
+ import {
11
+ GET_SUBSCRIBERS,
12
+ POST_SUBSCRIBE,
13
+ GET_SUBSCRIBER,
14
+ DELETE_SUBSCRIBER,
15
+ GET_SETTINGS,
16
+ POST_SETTINGS,
17
+ GET_NEWSLETTERS,
18
+ GET_NEWSLETTER,
19
+ POST_NEWSLETTER,
20
+ PUT_NEWSLETTER,
21
+ DELETE_NEWSLETTER,
22
+ GET_WELCOME_EMAIL_STATUS,
23
+ GET_WELCOME_EMAIL,
24
+ POST_WELCOME_EMAIL,
25
+ GET_SMTP_SETTINGS,
26
+ POST_SMTP_SETTINGS,
27
+ POST_TEST_EMAIL,
28
+ POST_LOGO_UPLOAD,
29
+ POST_SEND_NEWSLETTER,
30
+ GET_NEWSLETTER_FOR_SEND,
31
+ } from './handler';
32
+
33
+ /**
34
+ * Handle newsletter API requests
35
+ */
36
+ export async function handleNewsletterApi(
37
+ req: NextRequest,
38
+ path: string[],
39
+ config: NewsletterApiConfig
40
+ ): Promise<NextResponse> {
41
+ const method = req.method;
42
+ const route = path[0] || '';
43
+
44
+ try {
45
+ // Route: /api/plugin-newsletter/subscribers
46
+ if (route === 'subscribers') {
47
+ if (path[1]) {
48
+ // /api/plugin-newsletter/subscribers/[email]
49
+ const email = decodeURIComponent(path[1]).toLowerCase();
50
+ if (method === 'GET') {
51
+ return await GET_SUBSCRIBER(req, email, config);
52
+ }
53
+ if (method === 'DELETE') {
54
+ return await DELETE_SUBSCRIBER(req, email, config);
55
+ }
56
+ } else {
57
+ // /api/plugin-newsletter/subscribers
58
+ if (method === 'GET') {
59
+ return await GET_SUBSCRIBERS(req, config);
60
+ }
61
+ if (method === 'POST') {
62
+ return await POST_SUBSCRIBE(req, config);
63
+ }
64
+ }
65
+ }
66
+
67
+ // Route: /api/plugin-newsletter/settings
68
+ if (route === 'settings') {
69
+ if (method === 'GET') {
70
+ return await GET_SETTINGS(req, config);
71
+ }
72
+ if (method === 'POST' || method === 'PUT') {
73
+ return await POST_SETTINGS(req, config);
74
+ }
75
+ }
76
+
77
+ // Route: /api/plugin-newsletter/newsletters
78
+ if (route === 'newsletters') {
79
+ // Special route: /api/plugin-newsletter/newsletters/new
80
+ if (path[1] === 'new' && method === 'POST') {
81
+ return await POST_NEWSLETTER(req, config);
82
+ }
83
+
84
+ if (path[1]) {
85
+ const newsletterIdOrSlug = decodeURIComponent(path[1]);
86
+
87
+ // Special route: /api/plugin-newsletter/newsletters/[id]/send
88
+ if (path[2] === 'send' && method === 'POST') {
89
+ return await POST_SEND_NEWSLETTER(req, newsletterIdOrSlug, config);
90
+ }
91
+
92
+ // Special route: /api/plugin-newsletter/newsletters/[id]/send (GET for info)
93
+ if (path[2] === 'send' && method === 'GET') {
94
+ return await GET_NEWSLETTER_FOR_SEND(req, newsletterIdOrSlug, config);
95
+ }
96
+
97
+ // /api/plugin-newsletter/newsletters/[id-or-slug]
98
+ if (method === 'GET') {
99
+ return await GET_NEWSLETTER(req, newsletterIdOrSlug, config);
100
+ }
101
+ if (method === 'PUT') {
102
+ return await PUT_NEWSLETTER(req, newsletterIdOrSlug, config);
103
+ }
104
+ if (method === 'DELETE') {
105
+ return await DELETE_NEWSLETTER(req, newsletterIdOrSlug, config);
106
+ }
107
+ } else {
108
+ // /api/plugin-newsletter/newsletters
109
+ if (method === 'GET') {
110
+ return await GET_NEWSLETTERS(req, config);
111
+ }
112
+ if (method === 'POST') {
113
+ return await POST_NEWSLETTER(req, config);
114
+ }
115
+ }
116
+ }
117
+
118
+ // Route: /api/plugin-newsletter/welcome-email
119
+ if (route === 'welcome-email') {
120
+ // Status endpoint: /api/plugin-newsletter/welcome-email/status
121
+ if (path[1] === 'status' && method === 'GET') {
122
+ return await GET_WELCOME_EMAIL_STATUS(req, config);
123
+ }
124
+
125
+ // Welcome email content: /api/plugin-newsletter/welcome-email
126
+ if (method === 'GET') {
127
+ return await GET_WELCOME_EMAIL(req, config);
128
+ }
129
+ if (method === 'POST' || method === 'PUT') {
130
+ return await POST_WELCOME_EMAIL(req, config);
131
+ }
132
+ }
133
+
134
+ // Route: /api/plugin-newsletter/smtp
135
+ if (route === 'smtp') {
136
+ // Test email endpoint: /api/plugin-newsletter/smtp/test
137
+ if (path[1] === 'test' && method === 'POST') {
138
+ return await POST_TEST_EMAIL(req, config);
139
+ }
140
+ if (method === 'GET') {
141
+ return await GET_SMTP_SETTINGS(req, config);
142
+ }
143
+ if (method === 'POST' || method === 'PUT') {
144
+ return await POST_SMTP_SETTINGS(req, config);
145
+ }
146
+ }
147
+
148
+ // Route: /api/plugin-newsletter/upload
149
+ if (route === 'upload' && method === 'POST') {
150
+ return await POST_LOGO_UPLOAD(req, config);
151
+ }
152
+
153
+ // Method not allowed
154
+ return NextResponse.json(
155
+ { error: `Method ${method} not allowed for route: ${route || '/'}` },
156
+ { status: 405 }
157
+ );
158
+ } catch (error: any) {
159
+ console.error('[NewsletterApiRouter] Error:', error);
160
+ return NextResponse.json(
161
+ { error: error.message || 'Internal server error' },
162
+ { status: 500 }
163
+ );
164
+ }
165
+ }
166
+
@@ -0,0 +1,12 @@
1
+ import 'server-only';
2
+
3
+ /**
4
+ * Plugin Newsletter - Server-Only Entry Point
5
+ * This file exports only server-side API handlers
6
+ * Used by the dynamic plugin router via @jhits/plugin-newsletter/server
7
+ */
8
+
9
+ export { handleNewsletterApi as handleApi } from './api/router';
10
+ export { handleNewsletterApi } from './api/router'; // Keep original export for backward compatibility
11
+ export type { NewsletterApiConfig } from './types/newsletter';
12
+