@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.
- package/dist/api/email-utils.d.ts.map +1 -1
- package/dist/api/email-utils.js +45 -4
- package/dist/api/handlers/newsletters.d.ts.map +1 -1
- package/dist/api/handlers/newsletters.js +33 -16
- package/dist/api/handlers/send-newsletter.d.ts.map +1 -1
- package/dist/api/handlers/send-newsletter.js +54 -6
- package/dist/api/handlers/settings.d.ts.map +1 -1
- package/dist/api/handlers/settings.js +51 -1
- package/dist/index.d.ts +27 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -122
- package/dist/lib/blocks/BlockRenderer.d.ts.map +1 -1
- package/dist/lib/blocks/BlockRenderer.js +14 -2
- package/dist/lib/email/EmailRenderer.d.ts +1 -0
- package/dist/lib/email/EmailRenderer.d.ts.map +1 -1
- package/dist/lib/email/EmailRenderer.js +31 -19
- package/dist/lib/utils/config-resolver.d.ts +33 -0
- package/dist/lib/utils/config-resolver.d.ts.map +1 -0
- package/dist/lib/utils/config-resolver.js +47 -0
- package/dist/registry/BlockRegistry.d.ts +9 -1
- package/dist/registry/BlockRegistry.d.ts.map +1 -1
- package/dist/registry/BlockRegistry.js +126 -8
- package/dist/state/EditorContext.d.ts +11 -1
- package/dist/state/EditorContext.d.ts.map +1 -1
- package/dist/state/EditorContext.js +23 -5
- package/dist/state/types.d.ts +12 -0
- package/dist/state/types.d.ts.map +1 -1
- package/dist/types/block.d.ts +9 -0
- package/dist/types/block.d.ts.map +1 -1
- package/dist/types/newsletter.d.ts +4 -0
- package/dist/types/newsletter.d.ts.map +1 -1
- package/dist/views/CanvasEditor/BlockWrapper.d.ts.map +1 -1
- package/dist/views/CanvasEditor/BlockWrapper.js +24 -3
- package/dist/views/CanvasEditor/CanvasEditorView.d.ts.map +1 -1
- package/dist/views/CanvasEditor/CanvasEditorView.js +77 -17
- package/dist/views/CanvasEditor/EditorBody.d.ts.map +1 -1
- package/dist/views/CanvasEditor/EditorBody.js +1 -1
- package/dist/views/CanvasEditor/components/EditorCanvas.d.ts.map +1 -1
- package/dist/views/CanvasEditor/components/EditorCanvas.js +158 -100
- package/dist/views/CanvasEditor/components/EditorSidebar.d.ts +3 -1
- package/dist/views/CanvasEditor/components/EditorSidebar.d.ts.map +1 -1
- package/dist/views/CanvasEditor/components/EditorSidebar.js +3 -3
- package/dist/views/CanvasEditor/hooks/useRegisteredBlocks.d.ts +1 -1
- package/dist/views/CanvasEditor/hooks/useRegisteredBlocks.d.ts.map +1 -1
- package/dist/views/CanvasEditor/hooks/useRegisteredBlocks.js +6 -40
- package/dist/views/NewsletterManager.d.ts.map +1 -1
- package/dist/views/NewsletterManager.js +87 -5
- package/dist/views/components/DomainPromptModal.d.ts +13 -0
- package/dist/views/components/DomainPromptModal.d.ts.map +1 -0
- package/dist/views/components/DomainPromptModal.js +58 -0
- package/dist/views/components/NewsletterCard.d.ts +16 -0
- package/dist/views/components/NewsletterCard.d.ts.map +1 -0
- package/dist/views/components/NewsletterCard.js +94 -0
- package/dist/views/components/NewsletterGrid.d.ts +16 -0
- package/dist/views/components/NewsletterGrid.d.ts.map +1 -0
- package/dist/views/components/NewsletterGrid.js +13 -0
- package/dist/views/components/SendNewsletterModal.d.ts.map +1 -1
- package/dist/views/components/SendNewsletterModal.js +91 -22
- package/dist/views/components/SmtpSettingsModal.d.ts.map +1 -1
- package/dist/views/components/SmtpSettingsModal.js +10 -0
- package/dist/views/components/TestEmailModal.d.ts.map +1 -1
- package/dist/views/components/TestEmailModal.js +86 -17
- package/package.json +53 -9
- package/src/api/email-utils.ts +53 -4
- package/src/api/handlers/newsletters.ts +40 -20
- package/src/api/handlers/send-newsletter.ts +65 -6
- package/src/api/handlers/settings.ts +60 -2
- package/src/index.tsx +49 -155
- package/src/lib/blocks/BlockRenderer.tsx +16 -2
- package/src/lib/email/EmailRenderer.tsx +31 -20
- package/src/lib/utils/config-resolver.ts +71 -0
- package/src/registry/BlockRegistry.tsx +255 -0
- package/src/state/EditorContext.tsx +43 -8
- package/src/state/types.ts +16 -0
- package/src/types/block.ts +10 -0
- package/src/types/newsletter.ts +5 -0
- package/src/views/CanvasEditor/BlockWrapper.tsx +27 -2
- package/src/views/CanvasEditor/CanvasEditorView.tsx +142 -61
- package/src/views/CanvasEditor/EditorBody.tsx +17 -13
- package/src/views/CanvasEditor/components/EditorCanvas.tsx +178 -115
- package/src/views/CanvasEditor/components/EditorSidebar.tsx +57 -2
- package/src/views/CanvasEditor/hooks/useRegisteredBlocks.ts +6 -45
- package/src/views/NewsletterManager.tsx +164 -6
- package/src/views/components/DomainPromptModal.tsx +160 -0
- package/src/views/components/NewsletterCard.tsx +212 -0
- package/src/views/components/NewsletterGrid.tsx +48 -0
- package/src/views/components/SendNewsletterModal.tsx +270 -184
- package/src/views/components/SmtpSettingsModal.tsx +11 -0
- package/src/views/components/TestEmailModal.tsx +235 -149
- package/src/registry/BlockRegistry.ts +0 -53
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import React, { useState, useEffect } from 'react';
|
|
8
8
|
import { X, Send, RefreshCw, CheckCircle2, AlertCircle, Mail, Globe, Sparkles, MailCheck } from 'lucide-react';
|
|
9
|
+
import { DomainPromptModal } from './DomainPromptModal';
|
|
9
10
|
|
|
10
11
|
interface TestEmailModalProps {
|
|
11
12
|
isOpen: boolean;
|
|
@@ -29,10 +30,15 @@ export function TestEmailModal({ isOpen, onClose }: TestEmailModalProps) {
|
|
|
29
30
|
const [isSending, setIsSending] = useState(false);
|
|
30
31
|
const [sendSuccess, setSendSuccess] = useState(false);
|
|
31
32
|
const [sendError, setSendError] = useState<string | null>(null);
|
|
33
|
+
|
|
34
|
+
// Domain prompt state
|
|
35
|
+
const [showDomainPrompt, setShowDomainPrompt] = useState(false);
|
|
36
|
+
const [isCheckingDomain, setIsCheckingDomain] = useState(false);
|
|
32
37
|
|
|
33
38
|
useEffect(() => {
|
|
34
39
|
if (isOpen) {
|
|
35
40
|
fetchWelcomeEmailStatus();
|
|
41
|
+
setShowDomainPrompt(false);
|
|
36
42
|
}
|
|
37
43
|
}, [isOpen]);
|
|
38
44
|
|
|
@@ -53,7 +59,7 @@ export function TestEmailModal({ isOpen, onClose }: TestEmailModalProps) {
|
|
|
53
59
|
}
|
|
54
60
|
};
|
|
55
61
|
|
|
56
|
-
const handleSend = async () => {
|
|
62
|
+
const handleSend = async (skipDomainCheck = false) => {
|
|
57
63
|
if (!email.trim()) {
|
|
58
64
|
setSendError('Please enter an email address');
|
|
59
65
|
return;
|
|
@@ -65,6 +71,33 @@ export function TestEmailModal({ isOpen, onClose }: TestEmailModalProps) {
|
|
|
65
71
|
return;
|
|
66
72
|
}
|
|
67
73
|
|
|
74
|
+
// Check if we need to prompt for domain first (usually helpful in dev mode)
|
|
75
|
+
if (!skipDomainCheck) {
|
|
76
|
+
try {
|
|
77
|
+
setIsCheckingDomain(true);
|
|
78
|
+
// Check if this language has a domain configured
|
|
79
|
+
const response = await fetch('/api/plugin-website/settings');
|
|
80
|
+
const siteConfig = await response.json();
|
|
81
|
+
|
|
82
|
+
const hasDomain = siteConfig.domainLocaleConfig?.some((c: any) =>
|
|
83
|
+
c.locale === language &&
|
|
84
|
+
c.domain &&
|
|
85
|
+
c.domain !== 'undefined' &&
|
|
86
|
+
c.domain.trim() !== ''
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
if (!hasDomain) {
|
|
90
|
+
setShowDomainPrompt(true);
|
|
91
|
+
setIsCheckingDomain(false);
|
|
92
|
+
return; // Stop here, wait for domain prompt
|
|
93
|
+
}
|
|
94
|
+
} catch (error) {
|
|
95
|
+
console.warn('Failed to check domain configuration:', error);
|
|
96
|
+
} finally {
|
|
97
|
+
setIsCheckingDomain(false);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
68
101
|
try {
|
|
69
102
|
setIsSending(true);
|
|
70
103
|
setSendError(null);
|
|
@@ -79,6 +112,12 @@ export function TestEmailModal({ isOpen, onClose }: TestEmailModalProps) {
|
|
|
79
112
|
const data = await response.json();
|
|
80
113
|
|
|
81
114
|
if (!response.ok) {
|
|
115
|
+
// If server detects missing domain, trigger prompt
|
|
116
|
+
if (data.code === 'DOMAIN_MISSING') {
|
|
117
|
+
setShowDomainPrompt(true);
|
|
118
|
+
setIsSending(false);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
82
121
|
throw new Error(data.error || 'Failed to send test email');
|
|
83
122
|
}
|
|
84
123
|
|
|
@@ -96,173 +135,220 @@ export function TestEmailModal({ isOpen, onClose }: TestEmailModalProps) {
|
|
|
96
135
|
}
|
|
97
136
|
};
|
|
98
137
|
|
|
138
|
+
const handleSaveDomain = async (domain: string) => {
|
|
139
|
+
try {
|
|
140
|
+
// Get current site config
|
|
141
|
+
const response = await fetch('/api/plugin-website/settings');
|
|
142
|
+
const siteConfig = await response.json();
|
|
143
|
+
|
|
144
|
+
const currentConfigs = siteConfig.domainLocaleConfig || [];
|
|
145
|
+
const newConfigs = [...currentConfigs];
|
|
146
|
+
|
|
147
|
+
const existingIndex = newConfigs.findIndex((c: any) => c.locale === language);
|
|
148
|
+
if (existingIndex >= 0) {
|
|
149
|
+
newConfigs[existingIndex] = { ...newConfigs[existingIndex], domain };
|
|
150
|
+
} else {
|
|
151
|
+
newConfigs.push({ locale: language, domain });
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Save updated site config
|
|
155
|
+
const saveResponse = await fetch('/api/plugin-website/settings', {
|
|
156
|
+
method: 'POST',
|
|
157
|
+
headers: { 'Content-Type': 'application/json' },
|
|
158
|
+
body: JSON.stringify({
|
|
159
|
+
...siteConfig,
|
|
160
|
+
domainLocaleConfig: newConfigs
|
|
161
|
+
}),
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
if (!saveResponse.ok) throw new Error('Failed to save website settings');
|
|
165
|
+
|
|
166
|
+
// Re-trigger send with domain check skipped
|
|
167
|
+
setShowDomainPrompt(false);
|
|
168
|
+
handleSend(true);
|
|
169
|
+
} catch (error) {
|
|
170
|
+
console.error('Failed to save domain:', error);
|
|
171
|
+
throw error;
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
|
|
99
175
|
if (!isOpen) return null;
|
|
100
176
|
|
|
101
177
|
return (
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
<div className="flex items-center
|
|
114
|
-
<div className="
|
|
115
|
-
<
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
<
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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>
|
|
178
|
+
<>
|
|
179
|
+
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
|
180
|
+
{/* Backdrop */}
|
|
181
|
+
<div
|
|
182
|
+
className="absolute inset-0 bg-black/50 backdrop-blur-sm"
|
|
183
|
+
onClick={() => !isSending && onClose()}
|
|
184
|
+
/>
|
|
185
|
+
|
|
186
|
+
{/* Modal */}
|
|
187
|
+
<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">
|
|
188
|
+
{/* Header */}
|
|
189
|
+
<div className="flex items-center justify-between px-8 py-6 border-b border-dashboard-border">
|
|
190
|
+
<div className="flex items-center gap-3">
|
|
191
|
+
<div className="w-10 h-10 rounded-2xl bg-primary/10 flex items-center justify-center">
|
|
192
|
+
<Mail className="w-5 h-5 text-primary" />
|
|
193
|
+
</div>
|
|
194
|
+
<div>
|
|
195
|
+
<h2 className="text-xl font-black text-dashboard-text uppercase tracking-tight">
|
|
196
|
+
Test Email
|
|
197
|
+
</h2>
|
|
198
|
+
<p className="text-xs text-dashboard-text-secondary">
|
|
199
|
+
Send a test email to verify SMTP
|
|
200
|
+
</p>
|
|
172
201
|
</div>
|
|
173
202
|
</div>
|
|
203
|
+
<button
|
|
204
|
+
onClick={() => !isSending && onClose()}
|
|
205
|
+
disabled={isSending}
|
|
206
|
+
className="p-2 rounded-full hover:bg-dashboard-border transition-colors"
|
|
207
|
+
>
|
|
208
|
+
<X size={20} className="text-dashboard-text-secondary" />
|
|
209
|
+
</button>
|
|
210
|
+
</div>
|
|
174
211
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
<
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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) => (
|
|
212
|
+
{/* Content */}
|
|
213
|
+
<div className="p-8">
|
|
214
|
+
<div className="space-y-4">
|
|
215
|
+
{/* Email Type Toggle */}
|
|
216
|
+
<div>
|
|
217
|
+
<label className="text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest block mb-2">
|
|
218
|
+
Email Template
|
|
219
|
+
</label>
|
|
220
|
+
<div className="flex gap-2">
|
|
199
221
|
<button
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
language === lang.code
|
|
222
|
+
onClick={() => setEmailType('test')}
|
|
223
|
+
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 ${
|
|
224
|
+
emailType === 'test'
|
|
204
225
|
? 'bg-primary text-white'
|
|
205
226
|
: 'bg-dashboard-bg border border-dashboard-border text-dashboard-text hover:bg-dashboard-border'
|
|
206
227
|
}`}
|
|
207
228
|
>
|
|
208
|
-
{
|
|
229
|
+
<Mail size={14} />
|
|
230
|
+
Test
|
|
231
|
+
</button>
|
|
232
|
+
<button
|
|
233
|
+
onClick={() => setEmailType('welcome')}
|
|
234
|
+
disabled={!welcomeEmailConfigured}
|
|
235
|
+
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 ${
|
|
236
|
+
emailType === 'welcome'
|
|
237
|
+
? 'bg-primary text-white'
|
|
238
|
+
: welcomeEmailConfigured
|
|
239
|
+
? 'bg-dashboard-bg border border-dashboard-border text-dashboard-text hover:bg-dashboard-border'
|
|
240
|
+
: 'bg-dashboard-bg border border-dashboard-border text-dashboard-text-secondary opacity-50 cursor-not-allowed'
|
|
241
|
+
}`}
|
|
242
|
+
>
|
|
243
|
+
<Sparkles size={14} />
|
|
244
|
+
Welcome
|
|
245
|
+
{!welcomeEmailConfigured && (
|
|
246
|
+
<span className="text-[9px] normal-case opacity-70">(Not configured)</span>
|
|
247
|
+
)}
|
|
209
248
|
</button>
|
|
210
|
-
|
|
249
|
+
</div>
|
|
250
|
+
</div>
|
|
251
|
+
|
|
252
|
+
<div>
|
|
253
|
+
<label className="text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest block mb-2">
|
|
254
|
+
Recipient Email
|
|
255
|
+
</label>
|
|
256
|
+
<input
|
|
257
|
+
type="email"
|
|
258
|
+
value={email}
|
|
259
|
+
onChange={(e) => {
|
|
260
|
+
setEmail(e.target.value);
|
|
261
|
+
setSendError(null);
|
|
262
|
+
}}
|
|
263
|
+
onKeyDown={(e) => e.key === 'Enter' && handleSend()}
|
|
264
|
+
placeholder="your@email.com"
|
|
265
|
+
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"
|
|
266
|
+
/>
|
|
211
267
|
</div>
|
|
212
|
-
</div>
|
|
213
268
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
269
|
+
<div>
|
|
270
|
+
<label className="text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest block mb-2">
|
|
271
|
+
<Globe size={12} className="inline mr-1" />
|
|
272
|
+
Language
|
|
273
|
+
</label>
|
|
274
|
+
<div className="flex gap-2">
|
|
275
|
+
{languages.map((lang) => (
|
|
276
|
+
<button
|
|
277
|
+
key={lang.code}
|
|
278
|
+
onClick={() => setLanguage(lang.code)}
|
|
279
|
+
className={`px-4 py-2 rounded-xl text-xs font-bold uppercase tracking-widest transition-colors ${
|
|
280
|
+
language === lang.code
|
|
281
|
+
? 'bg-primary text-white'
|
|
282
|
+
: 'bg-dashboard-bg border border-dashboard-border text-dashboard-text hover:bg-dashboard-border'
|
|
283
|
+
}`}
|
|
284
|
+
>
|
|
285
|
+
{lang.label}
|
|
286
|
+
</button>
|
|
287
|
+
))}
|
|
288
|
+
</div>
|
|
218
289
|
</div>
|
|
219
|
-
)}
|
|
220
290
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
291
|
+
{sendError && (
|
|
292
|
+
<div className="flex items-center gap-2 text-red-500 text-sm">
|
|
293
|
+
<AlertCircle size={16} />
|
|
294
|
+
{sendError}
|
|
295
|
+
</div>
|
|
296
|
+
)}
|
|
297
|
+
|
|
298
|
+
<p className="text-xs text-dashboard-text-secondary">
|
|
299
|
+
A test email will be sent to this address using your configured SMTP settings.
|
|
300
|
+
</p>
|
|
301
|
+
</div>
|
|
224
302
|
</div>
|
|
225
|
-
</div>
|
|
226
303
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
304
|
+
{/* Footer */}
|
|
305
|
+
<div className="flex items-center justify-end gap-3 px-8 py-6 border-t border-dashboard-border bg-dashboard-bg/50">
|
|
306
|
+
<button
|
|
307
|
+
onClick={onClose}
|
|
308
|
+
disabled={isSending}
|
|
309
|
+
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"
|
|
310
|
+
>
|
|
311
|
+
Cancel
|
|
312
|
+
</button>
|
|
313
|
+
<button
|
|
314
|
+
onClick={() => handleSend()}
|
|
315
|
+
disabled={isSending || sendSuccess || isCheckingDomain}
|
|
316
|
+
className={`inline-flex items-center gap-2 px-6 py-3 rounded-full text-[10px] font-black uppercase tracking-widest transition-colors shadow-lg ${
|
|
317
|
+
isSending || isCheckingDomain
|
|
318
|
+
? 'bg-neutral-400 text-white cursor-not-allowed'
|
|
319
|
+
: sendSuccess
|
|
320
|
+
? 'bg-green-600 text-white'
|
|
321
|
+
: 'bg-primary text-white hover:bg-primary/90'
|
|
322
|
+
}`}
|
|
323
|
+
>
|
|
324
|
+
{isSending || isCheckingDomain ? (
|
|
325
|
+
<>
|
|
326
|
+
<RefreshCw className="w-4 h-4 animate-spin" />
|
|
327
|
+
{isCheckingDomain ? 'Checking...' : 'Sending...'}
|
|
328
|
+
</>
|
|
329
|
+
) : sendSuccess ? (
|
|
330
|
+
<>
|
|
331
|
+
<CheckCircle2 className="w-4 h-4" />
|
|
332
|
+
Sent!
|
|
333
|
+
</>
|
|
334
|
+
) : (
|
|
335
|
+
<>
|
|
336
|
+
<Send className="w-4 h-4" />
|
|
337
|
+
Send Test
|
|
338
|
+
</>
|
|
339
|
+
)}
|
|
340
|
+
</button>
|
|
341
|
+
</div>
|
|
264
342
|
</div>
|
|
265
343
|
</div>
|
|
266
|
-
|
|
344
|
+
|
|
345
|
+
{/* Domain Prompt Modal */}
|
|
346
|
+
<DomainPromptModal
|
|
347
|
+
isOpen={showDomainPrompt}
|
|
348
|
+
onClose={() => setShowDomainPrompt(false)}
|
|
349
|
+
onSave={handleSaveDomain}
|
|
350
|
+
language={language}
|
|
351
|
+
/>
|
|
352
|
+
</>
|
|
267
353
|
);
|
|
268
354
|
}
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Block Registry for Newsletter Plugin
|
|
3
|
-
* Dynamic registry for all block types in system
|
|
4
|
-
* Multi-Tenant Architecture: Blocks are provided by client applications
|
|
5
|
-
*
|
|
6
|
-
* The registry is a singleton that starts empty and is populated by client apps
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import {
|
|
10
|
-
BlockTypeDefinition,
|
|
11
|
-
ClientBlockDefinition
|
|
12
|
-
} from '../types/block';
|
|
13
|
-
|
|
14
|
-
// Local interface to avoid import issues
|
|
15
|
-
interface IBlockRegistry {
|
|
16
|
-
register(definition: BlockTypeDefinition): void;
|
|
17
|
-
get(type: string): BlockTypeDefinition | undefined;
|
|
18
|
-
getAll(): BlockTypeDefinition[];
|
|
19
|
-
has(type: string): boolean;
|
|
20
|
-
clear(): void;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Block Registry Implementation
|
|
25
|
-
* Singleton that manages all block types in the system
|
|
26
|
-
*/
|
|
27
|
-
class BlockRegistryImpl implements IBlockRegistry {
|
|
28
|
-
private blocks: Map<string, BlockTypeDefinition> = new Map();
|
|
29
|
-
|
|
30
|
-
register(definition: BlockTypeDefinition): void {
|
|
31
|
-
this.blocks.set(definition.type, definition);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
get(type: string): BlockTypeDefinition | undefined {
|
|
35
|
-
return this.blocks.get(type);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
getAll(): BlockTypeDefinition[] {
|
|
39
|
-
return Array.from(this.blocks.values());
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
has(type: string): boolean {
|
|
43
|
-
return this.blocks.has(type);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
clear(): void {
|
|
47
|
-
this.blocks.clear();
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Export singleton instance
|
|
52
|
-
export const BlockRegistry = new BlockRegistryImpl();
|
|
53
|
-
export const blockRegistry = BlockRegistry;
|