@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,268 @@
1
+ /**
2
+ * Test Email Modal Component
3
+ */
4
+
5
+ 'use client';
6
+
7
+ import React, { useState, useEffect } from 'react';
8
+ import { X, Send, RefreshCw, CheckCircle2, AlertCircle, Mail, Globe, Sparkles, MailCheck } from 'lucide-react';
9
+
10
+ interface TestEmailModalProps {
11
+ isOpen: boolean;
12
+ onClose: () => void;
13
+ }
14
+
15
+ type Language = 'en' | 'nl' | 'sv';
16
+ type EmailType = 'test' | 'welcome';
17
+
18
+ const languages: { code: Language; label: string }[] = [
19
+ { code: 'en', label: 'EN' },
20
+ { code: 'nl', label: 'NL' },
21
+ { code: 'sv', label: 'SV' },
22
+ ];
23
+
24
+ export function TestEmailModal({ isOpen, onClose }: TestEmailModalProps) {
25
+ const [email, setEmail] = useState('');
26
+ const [language, setLanguage] = useState<Language>('en');
27
+ const [emailType, setEmailType] = useState<EmailType>('test');
28
+ const [welcomeEmailConfigured, setWelcomeEmailConfigured] = useState(false);
29
+ const [isSending, setIsSending] = useState(false);
30
+ const [sendSuccess, setSendSuccess] = useState(false);
31
+ const [sendError, setSendError] = useState<string | null>(null);
32
+
33
+ useEffect(() => {
34
+ if (isOpen) {
35
+ fetchWelcomeEmailStatus();
36
+ }
37
+ }, [isOpen]);
38
+
39
+ const fetchWelcomeEmailStatus = async () => {
40
+ try {
41
+ const response = await fetch('/api/plugin-newsletter/welcome-email/status', {
42
+ credentials: 'include',
43
+ });
44
+ if (response.ok) {
45
+ const data = await response.json();
46
+ setWelcomeEmailConfigured(data.configured || false);
47
+ if (data.primaryLanguage) {
48
+ setLanguage(data.primaryLanguage as Language);
49
+ }
50
+ }
51
+ } catch (error) {
52
+ console.error('Failed to load welcome email status:', error);
53
+ }
54
+ };
55
+
56
+ const handleSend = async () => {
57
+ if (!email.trim()) {
58
+ setSendError('Please enter an email address');
59
+ return;
60
+ }
61
+
62
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
63
+ if (!emailRegex.test(email)) {
64
+ setSendError('Please enter a valid email address');
65
+ return;
66
+ }
67
+
68
+ try {
69
+ setIsSending(true);
70
+ setSendError(null);
71
+
72
+ const response = await fetch('/api/plugin-newsletter/smtp/test', {
73
+ method: 'POST',
74
+ headers: { 'Content-Type': 'application/json' },
75
+ credentials: 'include',
76
+ body: JSON.stringify({ email, language, emailType }),
77
+ });
78
+
79
+ const data = await response.json();
80
+
81
+ if (!response.ok) {
82
+ throw new Error(data.error || 'Failed to send test email');
83
+ }
84
+
85
+ setSendSuccess(true);
86
+ setTimeout(() => {
87
+ setSendSuccess(false);
88
+ onClose();
89
+ setEmail('');
90
+ }, 2000);
91
+ } catch (error: any) {
92
+ console.error('Failed to send test email:', error);
93
+ setSendError(error.message || 'Failed to send test email');
94
+ } finally {
95
+ setIsSending(false);
96
+ }
97
+ };
98
+
99
+ if (!isOpen) return null;
100
+
101
+ return (
102
+ <div className="fixed inset-0 z-50 flex items-center justify-center">
103
+ {/* Backdrop */}
104
+ <div
105
+ className="absolute inset-0 bg-black/50 backdrop-blur-sm"
106
+ onClick={() => !isSending && onClose()}
107
+ />
108
+
109
+ {/* Modal */}
110
+ <div className="relative w-full max-w-md mx-4 bg-white dark:bg-neutral-900 rounded-3xl border border-dashboard-border shadow-2xl overflow-hidden">
111
+ {/* Header */}
112
+ <div className="flex items-center justify-between px-8 py-6 border-b border-dashboard-border">
113
+ <div className="flex items-center gap-3">
114
+ <div className="w-10 h-10 rounded-2xl bg-primary/10 flex items-center justify-center">
115
+ <Mail className="w-5 h-5 text-primary" />
116
+ </div>
117
+ <div>
118
+ <h2 className="text-xl font-black text-dashboard-text uppercase tracking-tight">
119
+ Test Email
120
+ </h2>
121
+ <p className="text-xs text-dashboard-text-secondary">
122
+ Send a test email to verify SMTP
123
+ </p>
124
+ </div>
125
+ </div>
126
+ <button
127
+ onClick={() => !isSending && onClose()}
128
+ disabled={isSending}
129
+ className="p-2 rounded-full hover:bg-dashboard-border transition-colors"
130
+ >
131
+ <X size={20} className="text-dashboard-text-secondary" />
132
+ </button>
133
+ </div>
134
+
135
+ {/* Content */}
136
+ <div className="p-8">
137
+ <div className="space-y-4">
138
+ {/* Email Type Toggle */}
139
+ <div>
140
+ <label className="text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest block mb-2">
141
+ Email Template
142
+ </label>
143
+ <div className="flex gap-2">
144
+ <button
145
+ onClick={() => setEmailType('test')}
146
+ className={`flex-1 flex items-center justify-center gap-2 px-4 py-3 rounded-xl text-xs font-bold uppercase tracking-widest transition-colors ${
147
+ emailType === 'test'
148
+ ? 'bg-primary text-white'
149
+ : 'bg-dashboard-bg border border-dashboard-border text-dashboard-text hover:bg-dashboard-border'
150
+ }`}
151
+ >
152
+ <Mail size={14} />
153
+ Test
154
+ </button>
155
+ <button
156
+ onClick={() => setEmailType('welcome')}
157
+ disabled={!welcomeEmailConfigured}
158
+ className={`flex-1 flex items-center justify-center gap-2 px-4 py-3 rounded-xl text-xs font-bold uppercase tracking-widest transition-colors ${
159
+ emailType === 'welcome'
160
+ ? 'bg-primary text-white'
161
+ : welcomeEmailConfigured
162
+ ? 'bg-dashboard-bg border border-dashboard-border text-dashboard-text hover:bg-dashboard-border'
163
+ : 'bg-dashboard-bg border border-dashboard-border text-dashboard-text-secondary opacity-50 cursor-not-allowed'
164
+ }`}
165
+ >
166
+ <Sparkles size={14} />
167
+ Welcome
168
+ {!welcomeEmailConfigured && (
169
+ <span className="text-[9px] normal-case opacity-70">(Not configured)</span>
170
+ )}
171
+ </button>
172
+ </div>
173
+ </div>
174
+
175
+ <div>
176
+ <label className="text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest block mb-2">
177
+ Recipient Email
178
+ </label>
179
+ <input
180
+ type="email"
181
+ value={email}
182
+ onChange={(e) => {
183
+ setEmail(e.target.value);
184
+ setSendError(null);
185
+ }}
186
+ onKeyDown={(e) => e.key === 'Enter' && handleSend()}
187
+ placeholder="your@email.com"
188
+ className="w-full px-4 py-3 bg-dashboard-bg border border-dashboard-border rounded-xl outline-none focus:ring-2 focus:ring-primary transition-colors text-dashboard-text"
189
+ />
190
+ </div>
191
+
192
+ <div>
193
+ <label className="text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest block mb-2">
194
+ <Globe size={12} className="inline mr-1" />
195
+ Language
196
+ </label>
197
+ <div className="flex gap-2">
198
+ {languages.map((lang) => (
199
+ <button
200
+ key={lang.code}
201
+ onClick={() => setLanguage(lang.code)}
202
+ className={`px-4 py-2 rounded-xl text-xs font-bold uppercase tracking-widest transition-colors ${
203
+ language === lang.code
204
+ ? 'bg-primary text-white'
205
+ : 'bg-dashboard-bg border border-dashboard-border text-dashboard-text hover:bg-dashboard-border'
206
+ }`}
207
+ >
208
+ {lang.label}
209
+ </button>
210
+ ))}
211
+ </div>
212
+ </div>
213
+
214
+ {sendError && (
215
+ <div className="flex items-center gap-2 text-red-500 text-sm">
216
+ <AlertCircle size={16} />
217
+ {sendError}
218
+ </div>
219
+ )}
220
+
221
+ <p className="text-xs text-dashboard-text-secondary">
222
+ A test email will be sent to this address using your configured SMTP settings.
223
+ </p>
224
+ </div>
225
+ </div>
226
+
227
+ {/* Footer */}
228
+ <div className="flex items-center justify-end gap-3 px-8 py-6 border-t border-dashboard-border bg-dashboard-bg/50">
229
+ <button
230
+ onClick={onClose}
231
+ disabled={isSending}
232
+ className="px-6 py-3 rounded-full text-[10px] font-black uppercase tracking-widest transition-colors bg-dashboard-bg border border-dashboard-border text-dashboard-text hover:bg-dashboard-border"
233
+ >
234
+ Cancel
235
+ </button>
236
+ <button
237
+ onClick={handleSend}
238
+ disabled={isSending || sendSuccess}
239
+ className={`inline-flex items-center gap-2 px-6 py-3 rounded-full text-[10px] font-black uppercase tracking-widest transition-colors shadow-lg ${
240
+ isSending
241
+ ? 'bg-neutral-400 text-white cursor-not-allowed'
242
+ : sendSuccess
243
+ ? 'bg-green-600 text-white'
244
+ : 'bg-primary text-white hover:bg-primary/90'
245
+ }`}
246
+ >
247
+ {isSending ? (
248
+ <>
249
+ <RefreshCw className="w-4 h-4 animate-spin" />
250
+ Sending...
251
+ </>
252
+ ) : sendSuccess ? (
253
+ <>
254
+ <CheckCircle2 className="w-4 h-4" />
255
+ Sent!
256
+ </>
257
+ ) : (
258
+ <>
259
+ <Send className="w-4 h-4" />
260
+ Send Test
261
+ </>
262
+ )}
263
+ </button>
264
+ </div>
265
+ </div>
266
+ </div>
267
+ );
268
+ }