@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.
- package/data/locales/en/common.json +12 -0
- package/data/locales/nl/common.json +12 -0
- package/data/locales/sv/common.json +12 -0
- package/dist/api/email-utils.d.ts +6 -0
- package/dist/api/email-utils.d.ts.map +1 -0
- package/dist/api/email-utils.js +134 -0
- package/dist/api/handler.d.ts +2 -47
- package/dist/api/handler.d.ts.map +1 -1
- package/dist/api/handler.js +2 -523
- package/dist/api/handlers/index.d.ts +12 -0
- package/dist/api/handlers/index.d.ts.map +1 -0
- package/dist/api/handlers/index.js +11 -0
- package/dist/api/handlers/newsletters.d.ts +11 -0
- package/dist/api/handlers/newsletters.d.ts.map +1 -0
- package/dist/api/handlers/newsletters.js +250 -0
- package/dist/api/handlers/send-newsletter.d.ts +8 -0
- package/dist/api/handlers/send-newsletter.d.ts.map +1 -0
- package/dist/api/handlers/send-newsletter.js +206 -0
- package/dist/api/handlers/settings.d.ts +11 -0
- package/dist/api/handlers/settings.d.ts.map +1 -0
- package/dist/api/handlers/settings.js +304 -0
- package/dist/api/handlers/subscribers.d.ts +10 -0
- package/dist/api/handlers/subscribers.d.ts.map +1 -0
- package/dist/api/handlers/subscribers.js +96 -0
- package/dist/api/handlers/upload.d.ts +7 -0
- package/dist/api/handlers/upload.d.ts.map +1 -0
- package/dist/api/handlers/upload.js +32 -0
- package/dist/api/handlers/welcome-email.d.ts +13 -0
- package/dist/api/handlers/welcome-email.d.ts.map +1 -0
- package/dist/api/handlers/welcome-email.js +142 -0
- package/dist/api/router.d.ts.map +1 -1
- package/dist/api/router.js +45 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +42 -16
- package/dist/lib/email/EmailRenderer.d.ts.map +1 -1
- package/dist/lib/email/EmailRenderer.js +3 -7
- package/dist/lib/i18n.d.ts +16 -0
- package/dist/lib/i18n.d.ts.map +1 -0
- package/dist/lib/i18n.js +60 -0
- package/dist/state/EditorContext.d.ts +3 -1
- package/dist/state/EditorContext.d.ts.map +1 -1
- package/dist/state/EditorContext.js +1 -5
- package/dist/state/reducer.js +5 -5
- package/dist/types/newsletter.d.ts +1 -0
- package/dist/types/newsletter.d.ts.map +1 -1
- package/dist/views/CanvasEditor/CanvasEditorView.d.ts +4 -2
- package/dist/views/CanvasEditor/CanvasEditorView.d.ts.map +1 -1
- package/dist/views/CanvasEditor/CanvasEditorView.js +73 -10
- package/dist/views/CanvasEditor/EditorHeader.d.ts +6 -1
- package/dist/views/CanvasEditor/EditorHeader.d.ts.map +1 -1
- package/dist/views/CanvasEditor/EditorHeader.js +66 -11
- package/dist/views/CanvasEditor/components/EditorSidebar.d.ts +6 -2
- 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/useNewsletterLoader.d.ts +1 -1
- package/dist/views/CanvasEditor/hooks/useNewsletterLoader.d.ts.map +1 -1
- package/dist/views/CanvasEditor/hooks/useNewsletterLoader.js +41 -5
- package/dist/views/NewsletterEditor.d.ts +4 -2
- package/dist/views/NewsletterEditor.d.ts.map +1 -1
- package/dist/views/NewsletterEditor.js +2 -2
- package/dist/views/NewsletterManager.d.ts.map +1 -1
- package/dist/views/NewsletterManager.js +137 -8
- package/dist/views/components/SendNewsletterModal.d.ts +18 -0
- package/dist/views/components/SendNewsletterModal.d.ts.map +1 -0
- package/dist/views/components/SendNewsletterModal.js +107 -0
- package/dist/views/components/SmtpSettingsModal.d.ts +10 -0
- package/dist/views/components/SmtpSettingsModal.d.ts.map +1 -0
- package/dist/views/components/SmtpSettingsModal.js +145 -0
- package/dist/views/components/TestEmailModal.d.ts +10 -0
- package/dist/views/components/TestEmailModal.d.ts.map +1 -0
- package/dist/views/components/TestEmailModal.js +99 -0
- package/package.json +20 -9
- package/templates/logo.png +0 -0
- package/templates/test-email.hbs +221 -0
- package/templates/welcome-email-legacy.hbs +35 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Send Newsletter Modal Component
|
|
3
|
+
*/
|
|
4
|
+
'use client';
|
|
5
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
6
|
+
import { useState, useEffect } from 'react';
|
|
7
|
+
import { X, Send, RefreshCw, CheckCircle2, AlertCircle, Mail, Globe, Users } from 'lucide-react';
|
|
8
|
+
const languages = [
|
|
9
|
+
{ code: 'en', label: 'EN' },
|
|
10
|
+
{ code: 'nl', label: 'NL' },
|
|
11
|
+
{ code: 'sv', label: 'SV' },
|
|
12
|
+
];
|
|
13
|
+
export function SendNewsletterModal({ isOpen, onClose, newsletter, subscriberCount }) {
|
|
14
|
+
const [language, setLanguage] = useState('en');
|
|
15
|
+
const [sendMode, setSendMode] = useState('subscribers');
|
|
16
|
+
const [testEmail, setTestEmail] = useState('');
|
|
17
|
+
const [isSending, setIsSending] = useState(false);
|
|
18
|
+
const [sendSuccess, setSendSuccess] = useState(false);
|
|
19
|
+
const [sendError, setSendError] = useState(null);
|
|
20
|
+
const [resultDetails, setResultDetails] = useState(null);
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
if (isOpen) {
|
|
23
|
+
setSendMode('subscribers');
|
|
24
|
+
setSendError(null);
|
|
25
|
+
setResultDetails(null);
|
|
26
|
+
setSendSuccess(false);
|
|
27
|
+
}
|
|
28
|
+
}, [isOpen]);
|
|
29
|
+
const handleSend = async () => {
|
|
30
|
+
if (sendMode === 'test' && !testEmail.trim()) {
|
|
31
|
+
setSendError('Please enter an email address');
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (sendMode === 'test') {
|
|
35
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
36
|
+
if (!emailRegex.test(testEmail)) {
|
|
37
|
+
setSendError('Please enter a valid email address');
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
setIsSending(true);
|
|
43
|
+
setSendError(null);
|
|
44
|
+
const response = await fetch(`/api/plugin-newsletter/newsletters/${newsletter.id}/send`, {
|
|
45
|
+
method: 'POST',
|
|
46
|
+
headers: { 'Content-Type': 'application/json' },
|
|
47
|
+
credentials: 'include',
|
|
48
|
+
body: JSON.stringify({
|
|
49
|
+
language,
|
|
50
|
+
testEmail: sendMode === 'test' ? testEmail : undefined
|
|
51
|
+
}),
|
|
52
|
+
});
|
|
53
|
+
const data = await response.json();
|
|
54
|
+
if (!response.ok) {
|
|
55
|
+
throw new Error(data.error || 'Failed to send newsletter');
|
|
56
|
+
}
|
|
57
|
+
setResultDetails(data.details);
|
|
58
|
+
setSendSuccess(true);
|
|
59
|
+
if (sendMode === 'subscribers') {
|
|
60
|
+
setTimeout(() => {
|
|
61
|
+
onClose();
|
|
62
|
+
window.location.reload();
|
|
63
|
+
}, 2000);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
setTimeout(() => {
|
|
67
|
+
setSendSuccess(false);
|
|
68
|
+
setTestEmail('');
|
|
69
|
+
}, 3000);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
console.error('Failed to send newsletter:', error);
|
|
74
|
+
setSendError(error.message || 'Failed to send newsletter');
|
|
75
|
+
}
|
|
76
|
+
finally {
|
|
77
|
+
setIsSending(false);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
if (!isOpen)
|
|
81
|
+
return null;
|
|
82
|
+
const canSend = newsletter.hasContent && (sendMode === 'test' || subscriberCount > 0);
|
|
83
|
+
const isAlreadySent = newsletter.status === 'sent';
|
|
84
|
+
const noSubscribersError = sendMode === 'subscribers' && subscriberCount === 0;
|
|
85
|
+
return (_jsxs("div", { className: "fixed inset-0 z-50 flex items-center justify-center", children: [_jsx("div", { className: "absolute inset-0 bg-black/50 backdrop-blur-sm", onClick: () => !isSending && onClose() }), _jsxs("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", children: [_jsxs("div", { className: "flex items-center justify-between px-8 py-6 border-b border-dashboard-border", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "w-10 h-10 rounded-2xl bg-primary/10 flex items-center justify-center", children: _jsx(Send, { className: "w-5 h-5 text-primary" }) }), _jsxs("div", { children: [_jsx("h2", { className: "text-xl font-black text-dashboard-text uppercase tracking-tight", children: "Send Newsletter" }), _jsx("p", { className: "text-xs text-dashboard-text-secondary truncate max-w-[200px]", children: newsletter.title })] })] }), _jsx("button", { onClick: () => !isSending && onClose(), disabled: isSending, className: "p-2 rounded-full hover:bg-dashboard-border transition-colors", children: _jsx(X, { size: 20, className: "text-dashboard-text-secondary" }) })] }), _jsxs("div", { className: "p-8", children: [isAlreadySent && (_jsx("div", { className: "mb-4 p-4 rounded-xl bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800", children: _jsxs("div", { className: "flex items-center gap-2 text-amber-700 dark:text-amber-400", children: [_jsx(AlertCircle, { size: 16 }), _jsx("span", { className: "text-xs font-bold uppercase", children: "This newsletter has already been sent" })] }) })), !newsletter.hasContent && (_jsx("div", { className: "mb-4 p-4 rounded-xl bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800", children: _jsxs("div", { className: "flex items-center gap-2 text-red-700 dark:text-red-400", children: [_jsx(AlertCircle, { size: 16 }), _jsx("span", { className: "text-xs font-bold uppercase", children: "This newsletter has no content" })] }) })), _jsxs("div", { className: "space-y-4", children: [_jsxs("div", { children: [_jsx("label", { className: "text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest block mb-2", children: "Send To" }), _jsxs("div", { className: "flex gap-2", children: [_jsxs("button", { onClick: () => setSendMode('subscribers'), 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 ${sendMode === 'subscribers'
|
|
86
|
+
? 'bg-primary text-white'
|
|
87
|
+
: subscriberCount > 0
|
|
88
|
+
? 'bg-dashboard-bg border border-dashboard-border text-dashboard-text hover:bg-dashboard-border'
|
|
89
|
+
: 'bg-dashboard-bg border border-dashboard-border text-dashboard-text-secondary opacity-50'}`, children: [_jsx(Users, { size: 14 }), "Subscribers", subscriberCount > 0 && (_jsxs("span", { className: "text-[10px] opacity-70", children: ["(", subscriberCount, ")"] }))] }), _jsxs("button", { onClick: () => setSendMode('test'), 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 ${sendMode === 'test'
|
|
90
|
+
? 'bg-primary text-white'
|
|
91
|
+
: 'bg-dashboard-bg border border-dashboard-border text-dashboard-text hover:bg-dashboard-border'}`, children: [_jsx(Mail, { size: 14 }), "Test"] })] })] }), sendMode === 'test' && (_jsxs("div", { children: [_jsx("label", { className: "text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest block mb-2", children: "Test Email Address" }), _jsx("input", { type: "email", value: testEmail, onChange: (e) => {
|
|
92
|
+
setTestEmail(e.target.value);
|
|
93
|
+
setSendError(null);
|
|
94
|
+
}, onKeyDown: (e) => e.key === 'Enter' && handleSend(), placeholder: "your@email.com", 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" })] })), _jsxs("div", { children: [_jsxs("label", { className: "text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest block mb-2", children: [_jsx(Globe, { size: 12, className: "inline mr-1" }), "Language"] }), _jsx("div", { className: "flex gap-2", children: languages.map((lang) => (_jsx("button", { onClick: () => setLanguage(lang.code), className: `px-4 py-2 rounded-xl text-xs font-bold uppercase tracking-widest transition-colors ${language === lang.code
|
|
95
|
+
? 'bg-primary text-white'
|
|
96
|
+
: 'bg-dashboard-bg border border-dashboard-border text-dashboard-text hover:bg-dashboard-border'}`, children: lang.label }, lang.code))) })] }), sendError && (_jsxs("div", { className: "flex items-center gap-2 text-red-500 text-sm", children: [_jsx(AlertCircle, { size: 16 }), sendError] })), noSubscribersError && (_jsxs("div", { className: "flex items-center gap-2 text-amber-600 dark:text-amber-400 text-sm", children: [_jsx(AlertCircle, { size: 16 }), "No subscribers found. Add subscribers or use Test mode."] })), sendSuccess && resultDetails && (_jsxs("div", { className: "flex items-center gap-2 text-green-600 dark:text-green-400 text-sm", children: [_jsx(CheckCircle2, { size: 16 }), sendMode === 'test'
|
|
97
|
+
? `Test newsletter sent successfully!`
|
|
98
|
+
: `Sent to ${resultDetails.successCount} subscriber${resultDetails.successCount !== 1 ? 's' : ''}${resultDetails.failedCount > 0 ? ` (${resultDetails.failedCount} failed)` : ''}`] })), !sendSuccess && (_jsx("p", { className: "text-xs text-dashboard-text-secondary", children: sendMode === 'subscribers'
|
|
99
|
+
? `This will send the newsletter to all ${subscriberCount} active subscriber${subscriberCount !== 1 ? 's' : ''}.`
|
|
100
|
+
: 'Send a test newsletter to verify the email looks correct.' }))] })] }), _jsxs("div", { className: "flex items-center justify-end gap-3 px-8 py-6 border-t border-dashboard-border bg-dashboard-bg/50", children: [_jsx("button", { onClick: onClose, disabled: isSending, 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", children: sendSuccess ? 'Close' : 'Cancel' }), _jsx("button", { onClick: handleSend, disabled: isSending || sendSuccess || !canSend, className: `inline-flex items-center gap-2 px-6 py-3 rounded-full text-[10px] font-black uppercase tracking-widest transition-colors shadow-lg ${isSending
|
|
101
|
+
? 'bg-neutral-400 text-white cursor-not-allowed'
|
|
102
|
+
: sendSuccess
|
|
103
|
+
? 'bg-green-600 text-white'
|
|
104
|
+
: !canSend
|
|
105
|
+
? 'bg-neutral-400 text-white cursor-not-allowed'
|
|
106
|
+
: 'bg-primary text-white hover:bg-primary/90'}`, children: isSending ? (_jsxs(_Fragment, { children: [_jsx(RefreshCw, { className: "w-4 h-4 animate-spin" }), "Sending..."] })) : sendSuccess ? (_jsxs(_Fragment, { children: [_jsx(CheckCircle2, { className: "w-4 h-4" }), "Sent!"] })) : (_jsxs(_Fragment, { children: [_jsx(Send, { className: "w-4 h-4" }), sendMode === 'subscribers' ? 'Send to Subscribers' : 'Send Test'] })) })] })] })] }));
|
|
107
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SMTP Settings Modal Component
|
|
3
|
+
*/
|
|
4
|
+
interface SmtpSettingsModalProps {
|
|
5
|
+
isOpen: boolean;
|
|
6
|
+
onClose: () => void;
|
|
7
|
+
}
|
|
8
|
+
export declare function SmtpSettingsModal({ isOpen, onClose }: SmtpSettingsModalProps): import("react/jsx-runtime").JSX.Element | null;
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=SmtpSettingsModal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SmtpSettingsModal.d.ts","sourceRoot":"","sources":["../../../src/views/components/SmtpSettingsModal.tsx"],"names":[],"mappings":"AAAA;;GAEG;AAkBH,UAAU,sBAAsB;IAC5B,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,MAAM,IAAI,CAAC;CACvB;AAED,wBAAgB,iBAAiB,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,sBAAsB,kDAuZ5E"}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SMTP Settings Modal Component
|
|
3
|
+
*/
|
|
4
|
+
'use client';
|
|
5
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
6
|
+
import { useState, useEffect } from 'react';
|
|
7
|
+
import { Settings2, X, Save, Eye, EyeOff, RefreshCw, CheckCircle2, Pencil, Unlink, Image as ImageIcon, Upload, XCircle } from 'lucide-react';
|
|
8
|
+
export function SmtpSettingsModal({ isOpen, onClose }) {
|
|
9
|
+
const [smtpSettings, setSmtpSettings] = useState({
|
|
10
|
+
host: '',
|
|
11
|
+
port: 465,
|
|
12
|
+
user: '',
|
|
13
|
+
password: '',
|
|
14
|
+
from: '',
|
|
15
|
+
fromName: '',
|
|
16
|
+
primaryLanguage: 'en',
|
|
17
|
+
logoUrl: '',
|
|
18
|
+
});
|
|
19
|
+
const [smtpLoading, setSmtpLoading] = useState(true);
|
|
20
|
+
const [smtpSaving, setSmtpSaving] = useState(false);
|
|
21
|
+
const [smtpShowPassword, setSmtpShowPassword] = useState(false);
|
|
22
|
+
const [smtpSuccess, setSmtpSuccess] = useState(false);
|
|
23
|
+
const [fromEmailEditable, setFromEmailEditable] = useState(false);
|
|
24
|
+
const [isLogoUploading, setIsLogoUploading] = useState(false);
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
if (isOpen) {
|
|
27
|
+
fetchSmtpSettings();
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
setFromEmailEditable(false);
|
|
31
|
+
}
|
|
32
|
+
}, [isOpen]);
|
|
33
|
+
const fetchSmtpSettings = async () => {
|
|
34
|
+
try {
|
|
35
|
+
setSmtpLoading(true);
|
|
36
|
+
const response = await fetch('/api/plugin-newsletter/smtp', {
|
|
37
|
+
credentials: 'include',
|
|
38
|
+
});
|
|
39
|
+
if (response.ok) {
|
|
40
|
+
const data = await response.json();
|
|
41
|
+
setSmtpSettings({
|
|
42
|
+
host: data.host || '',
|
|
43
|
+
port: data.port || 465,
|
|
44
|
+
user: data.user || '',
|
|
45
|
+
password: data.password || '',
|
|
46
|
+
from: data.from || '',
|
|
47
|
+
fromName: data.fromName || '',
|
|
48
|
+
primaryLanguage: data.primaryLanguage || 'en',
|
|
49
|
+
logoUrl: data.logoUrl || '',
|
|
50
|
+
});
|
|
51
|
+
setFromEmailEditable(!!data.from && data.from !== data.user);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
console.error('Failed to load SMTP settings:', error);
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
setSmtpLoading(false);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
const handleSave = async () => {
|
|
62
|
+
try {
|
|
63
|
+
setSmtpSaving(true);
|
|
64
|
+
const settingsToSave = {
|
|
65
|
+
...smtpSettings,
|
|
66
|
+
from: smtpSettings.from || smtpSettings.user,
|
|
67
|
+
};
|
|
68
|
+
const response = await fetch('/api/plugin-newsletter/smtp', {
|
|
69
|
+
method: 'POST',
|
|
70
|
+
headers: { 'Content-Type': 'application/json' },
|
|
71
|
+
credentials: 'include',
|
|
72
|
+
body: JSON.stringify(settingsToSave),
|
|
73
|
+
});
|
|
74
|
+
if (!response.ok) {
|
|
75
|
+
const error = await response.json();
|
|
76
|
+
throw new Error(error.error || 'Failed to save SMTP settings');
|
|
77
|
+
}
|
|
78
|
+
setSmtpSuccess(true);
|
|
79
|
+
setTimeout(() => {
|
|
80
|
+
setSmtpSuccess(false);
|
|
81
|
+
onClose();
|
|
82
|
+
}, 1500);
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
console.error('Failed to save SMTP settings:', error);
|
|
86
|
+
alert(error.message || 'Failed to save SMTP settings');
|
|
87
|
+
}
|
|
88
|
+
finally {
|
|
89
|
+
setSmtpSaving(false);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
const handleLogoUpload = async (e) => {
|
|
93
|
+
const file = e.target.files?.[0];
|
|
94
|
+
if (!file)
|
|
95
|
+
return;
|
|
96
|
+
const allowedTypes = ['image/png', 'image/jpeg'];
|
|
97
|
+
if (!allowedTypes.includes(file.type)) {
|
|
98
|
+
alert('Please upload a PNG or JPEG image');
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
if (file.size > 2 * 1024 * 1024) {
|
|
102
|
+
alert('Image must be smaller than 2MB');
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
setIsLogoUploading(true);
|
|
107
|
+
const formData = new FormData();
|
|
108
|
+
formData.append('file', file);
|
|
109
|
+
formData.append('type', 'logo');
|
|
110
|
+
const response = await fetch('/api/plugin-newsletter/upload', {
|
|
111
|
+
method: 'POST',
|
|
112
|
+
credentials: 'include',
|
|
113
|
+
body: formData,
|
|
114
|
+
});
|
|
115
|
+
if (!response.ok) {
|
|
116
|
+
const errorData = await response.json();
|
|
117
|
+
throw new Error(errorData.error || 'Failed to upload logo');
|
|
118
|
+
}
|
|
119
|
+
const data = await response.json();
|
|
120
|
+
setSmtpSettings(prev => ({ ...prev, logoUrl: data.url }));
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
console.error('Failed to upload logo:', error);
|
|
124
|
+
alert(error.message || 'Failed to upload logo. Please try again.');
|
|
125
|
+
}
|
|
126
|
+
finally {
|
|
127
|
+
setIsLogoUploading(false);
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
if (!isOpen)
|
|
131
|
+
return null;
|
|
132
|
+
return (_jsxs("div", { className: "fixed inset-0 z-50 flex items-center justify-center", children: [_jsx("div", { className: "absolute inset-0 bg-black/50 backdrop-blur-sm", onClick: () => !smtpSaving && onClose() }), _jsxs("div", { className: "relative w-full max-w-2xl mx-4 bg-white dark:bg-neutral-900 rounded-3xl border border-dashboard-border shadow-2xl overflow-hidden", children: [_jsxs("div", { className: "flex items-center justify-between px-8 py-6 border-b border-dashboard-border", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "w-10 h-10 rounded-2xl bg-primary/10 flex items-center justify-center", children: _jsx(Settings2, { className: "w-5 h-5 text-primary" }) }), _jsxs("div", { children: [_jsx("h2", { className: "text-xl font-black text-dashboard-text uppercase tracking-tight", children: "SMTP Settings" }), _jsx("p", { className: "text-xs text-dashboard-text-secondary", children: "Configure email server for sending newsletters" })] })] }), _jsx("button", { onClick: () => !smtpSaving && onClose(), disabled: smtpSaving, className: "p-2 rounded-full hover:bg-dashboard-border transition-colors", children: _jsx(X, { size: 20, className: "text-dashboard-text-secondary" }) })] }), _jsx("div", { className: "p-8 space-y-6 max-h-[60vh] overflow-y-auto", children: smtpLoading ? (_jsx("div", { className: "flex items-center justify-center py-12", children: _jsx(RefreshCw, { className: "w-6 h-6 animate-spin text-primary" }) })) : (_jsx(_Fragment, { children: _jsxs("div", { className: "grid grid-cols-2 gap-6", children: [_jsxs("div", { className: "col-span-2", children: [_jsx("label", { className: "text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest block mb-2", children: "SMTP Host" }), _jsx("input", { type: "text", value: smtpSettings.host, onChange: (e) => setSmtpSettings(prev => ({ ...prev, host: e.target.value })), placeholder: "smtp.example.com", 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" })] }), _jsxs("div", { children: [_jsx("label", { className: "text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest block mb-2", children: "Port" }), _jsx("input", { type: "number", value: smtpSettings.port, onChange: (e) => setSmtpSettings(prev => ({ ...prev, port: parseInt(e.target.value) || 465 })), placeholder: "465", 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" })] }), _jsxs("div", { children: [_jsx("label", { className: "text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest block mb-2", children: "From Name" }), _jsx("input", { type: "text", value: smtpSettings.fromName, onChange: (e) => setSmtpSettings(prev => ({ ...prev, fromName: e.target.value })), placeholder: "My Newsletter", 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" })] }), _jsxs("div", { className: "col-span-2", children: [_jsx("label", { className: "text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest block mb-2", children: "Username / Email" }), _jsx("input", { type: "text", value: smtpSettings.user, onChange: (e) => setSmtpSettings(prev => ({ ...prev, user: e.target.value })), placeholder: "smtp@example.com", 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" })] }), _jsxs("div", { className: "col-span-2", children: [_jsx("label", { className: "text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest block mb-2", children: "Password / App Password" }), _jsxs("div", { className: "relative", children: [_jsx("input", { type: smtpShowPassword ? 'text' : 'password', value: smtpSettings.password, onChange: (e) => setSmtpSettings(prev => ({ ...prev, password: e.target.value })), placeholder: "Enter password or app password", className: "w-full px-4 py-3 pr-12 bg-dashboard-bg border border-dashboard-border rounded-xl outline-none focus:ring-2 focus:ring-primary transition-colors text-dashboard-text" }), _jsx("button", { type: "button", onClick: () => setSmtpShowPassword(!smtpShowPassword), className: "absolute right-3 top-1/2 -translate-y-1/2 p-1 text-dashboard-text-secondary hover:text-dashboard-text", children: smtpShowPassword ? _jsx(EyeOff, { size: 18 }) : _jsx(Eye, { size: 18 }) })] })] }), _jsxs("div", { className: "col-span-2", children: [_jsxs("div", { className: "flex items-center justify-between mb-2", children: [_jsx("label", { className: "text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest", children: "From Email" }), _jsx("button", { type: "button", onClick: () => {
|
|
133
|
+
if (fromEmailEditable) {
|
|
134
|
+
setFromEmailEditable(false);
|
|
135
|
+
setSmtpSettings(prev => ({ ...prev, from: prev.user }));
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
setFromEmailEditable(true);
|
|
139
|
+
}
|
|
140
|
+
}, className: "inline-flex items-center gap-1.5 text-[10px] font-bold uppercase tracking-widest text-primary hover:underline", children: fromEmailEditable ? (_jsxs(_Fragment, { children: [_jsx(Unlink, { size: 12 }), "Use same as username"] })) : (_jsxs(_Fragment, { children: [_jsx(Pencil, { size: 12 }), "Edit"] })) })] }), _jsx("input", { type: "email", value: smtpSettings.from || smtpSettings.user, onChange: (e) => setSmtpSettings(prev => ({ ...prev, from: e.target.value })), disabled: !fromEmailEditable, placeholder: "newsletter@example.com", 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 ${!fromEmailEditable ? 'opacity-60 cursor-not-allowed' : ''}` }), !fromEmailEditable && smtpSettings.user && (_jsxs("p", { className: "text-[10px] text-dashboard-text-secondary mt-1", children: ["Using: ", smtpSettings.user] }))] }), _jsxs("div", { className: "col-span-2", children: [_jsx("label", { className: "text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest block mb-2", children: "Primary Language" }), _jsx("p", { className: "text-[10px] text-dashboard-text-secondary mb-2", children: "Default language for welcome emails and newsletters" }), _jsxs("select", { value: smtpSettings.primaryLanguage || 'en', onChange: (e) => setSmtpSettings(prev => ({ ...prev, primaryLanguage: e.target.value })), 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", children: [_jsx("option", { value: "en", children: "English" }), _jsx("option", { value: "nl", children: "Dutch" }), _jsx("option", { value: "sv", children: "Swedish" })] })] }), _jsxs("div", { className: "col-span-2", children: [_jsx("label", { className: "text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest block mb-2", children: _jsxs("span", { className: "flex items-center gap-2", children: [_jsx(ImageIcon, { size: 14 }), "Email Logo"] }) }), _jsx("p", { className: "text-[10px] text-dashboard-text-secondary mb-2", children: "Logo to use in email headers (recommended: 200x50px PNG/JPEG)" }), _jsx("div", { className: "border-2 border-dashboard-border border-dashed rounded-xl p-6 bg-dashboard-bg text-center", children: isLogoUploading ? (_jsxs("div", { className: "flex flex-col items-center gap-2 text-dashboard-text-secondary", children: [_jsx(RefreshCw, { size: 24, className: "animate-spin" }), _jsx("span", { className: "text-xs", children: "Uploading..." })] })) : smtpSettings.logoUrl ? (_jsxs("div", { className: "relative inline-block", children: [_jsx("img", { src: smtpSettings.logoUrl, alt: "Logo preview", className: "max-h-16 mx-auto" }), _jsx("button", { onClick: () => setSmtpSettings(prev => ({ ...prev, logoUrl: '' })), className: "absolute -top-2 -right-2 p-1 bg-red-500 text-white rounded-full hover:bg-red-600", children: _jsx(XCircle, { size: 16 }) })] })) : (_jsxs("label", { className: "cursor-pointer block", children: [_jsx("input", { type: "file", accept: "image/png,image/jpeg", onChange: handleLogoUpload, className: "hidden" }), _jsxs("div", { className: "flex flex-col items-center gap-2 text-dashboard-text-secondary hover:text-dashboard-text", children: [_jsx(Upload, { size: 24 }), _jsx("span", { className: "text-xs", children: "Click to upload logo" })] })] })) })] })] }) })) }), _jsxs("div", { className: "flex items-center justify-end gap-3 px-8 py-6 border-t border-dashboard-border bg-dashboard-bg/50", children: [_jsx("button", { onClick: onClose, disabled: smtpSaving, 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", children: "Cancel" }), _jsx("button", { onClick: handleSave, disabled: smtpSaving || smtpLoading, className: `inline-flex items-center gap-2 px-6 py-3 rounded-full text-[10px] font-black uppercase tracking-widest transition-colors shadow-lg ${smtpSaving
|
|
141
|
+
? 'bg-neutral-400 text-white cursor-not-allowed'
|
|
142
|
+
: smtpSuccess
|
|
143
|
+
? 'bg-green-600 text-white'
|
|
144
|
+
: 'bg-primary text-white hover:bg-primary/90'}`, children: smtpSaving ? (_jsxs(_Fragment, { children: [_jsx(RefreshCw, { className: "w-4 h-4 animate-spin" }), "Saving..."] })) : smtpSuccess ? (_jsxs(_Fragment, { children: [_jsx(CheckCircle2, { className: "w-4 h-4" }), "Saved!"] })) : (_jsxs(_Fragment, { children: [_jsx(Save, { className: "w-4 h-4" }), "Save Settings"] })) })] })] })] }));
|
|
145
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test Email Modal Component
|
|
3
|
+
*/
|
|
4
|
+
interface TestEmailModalProps {
|
|
5
|
+
isOpen: boolean;
|
|
6
|
+
onClose: () => void;
|
|
7
|
+
}
|
|
8
|
+
export declare function TestEmailModal({ isOpen, onClose }: TestEmailModalProps): import("react/jsx-runtime").JSX.Element | null;
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=TestEmailModal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TestEmailModal.d.ts","sourceRoot":"","sources":["../../../src/views/components/TestEmailModal.tsx"],"names":[],"mappings":"AAAA;;GAEG;AAOH,UAAU,mBAAmB;IACzB,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,MAAM,IAAI,CAAC;CACvB;AAWD,wBAAgB,cAAc,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,mBAAmB,kDAoPtE"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test Email Modal Component
|
|
3
|
+
*/
|
|
4
|
+
'use client';
|
|
5
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
6
|
+
import { useState, useEffect } from 'react';
|
|
7
|
+
import { X, Send, RefreshCw, CheckCircle2, AlertCircle, Mail, Globe, Sparkles } from 'lucide-react';
|
|
8
|
+
const languages = [
|
|
9
|
+
{ code: 'en', label: 'EN' },
|
|
10
|
+
{ code: 'nl', label: 'NL' },
|
|
11
|
+
{ code: 'sv', label: 'SV' },
|
|
12
|
+
];
|
|
13
|
+
export function TestEmailModal({ isOpen, onClose }) {
|
|
14
|
+
const [email, setEmail] = useState('');
|
|
15
|
+
const [language, setLanguage] = useState('en');
|
|
16
|
+
const [emailType, setEmailType] = useState('test');
|
|
17
|
+
const [welcomeEmailConfigured, setWelcomeEmailConfigured] = useState(false);
|
|
18
|
+
const [isSending, setIsSending] = useState(false);
|
|
19
|
+
const [sendSuccess, setSendSuccess] = useState(false);
|
|
20
|
+
const [sendError, setSendError] = useState(null);
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
if (isOpen) {
|
|
23
|
+
fetchWelcomeEmailStatus();
|
|
24
|
+
}
|
|
25
|
+
}, [isOpen]);
|
|
26
|
+
const fetchWelcomeEmailStatus = async () => {
|
|
27
|
+
try {
|
|
28
|
+
const response = await fetch('/api/plugin-newsletter/welcome-email/status', {
|
|
29
|
+
credentials: 'include',
|
|
30
|
+
});
|
|
31
|
+
if (response.ok) {
|
|
32
|
+
const data = await response.json();
|
|
33
|
+
setWelcomeEmailConfigured(data.configured || false);
|
|
34
|
+
if (data.primaryLanguage) {
|
|
35
|
+
setLanguage(data.primaryLanguage);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
console.error('Failed to load welcome email status:', error);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
const handleSend = async () => {
|
|
44
|
+
if (!email.trim()) {
|
|
45
|
+
setSendError('Please enter an email address');
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
49
|
+
if (!emailRegex.test(email)) {
|
|
50
|
+
setSendError('Please enter a valid email address');
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
setIsSending(true);
|
|
55
|
+
setSendError(null);
|
|
56
|
+
const response = await fetch('/api/plugin-newsletter/smtp/test', {
|
|
57
|
+
method: 'POST',
|
|
58
|
+
headers: { 'Content-Type': 'application/json' },
|
|
59
|
+
credentials: 'include',
|
|
60
|
+
body: JSON.stringify({ email, language, emailType }),
|
|
61
|
+
});
|
|
62
|
+
const data = await response.json();
|
|
63
|
+
if (!response.ok) {
|
|
64
|
+
throw new Error(data.error || 'Failed to send test email');
|
|
65
|
+
}
|
|
66
|
+
setSendSuccess(true);
|
|
67
|
+
setTimeout(() => {
|
|
68
|
+
setSendSuccess(false);
|
|
69
|
+
onClose();
|
|
70
|
+
setEmail('');
|
|
71
|
+
}, 2000);
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
console.error('Failed to send test email:', error);
|
|
75
|
+
setSendError(error.message || 'Failed to send test email');
|
|
76
|
+
}
|
|
77
|
+
finally {
|
|
78
|
+
setIsSending(false);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
if (!isOpen)
|
|
82
|
+
return null;
|
|
83
|
+
return (_jsxs("div", { className: "fixed inset-0 z-50 flex items-center justify-center", children: [_jsx("div", { className: "absolute inset-0 bg-black/50 backdrop-blur-sm", onClick: () => !isSending && onClose() }), _jsxs("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", children: [_jsxs("div", { className: "flex items-center justify-between px-8 py-6 border-b border-dashboard-border", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "w-10 h-10 rounded-2xl bg-primary/10 flex items-center justify-center", children: _jsx(Mail, { className: "w-5 h-5 text-primary" }) }), _jsxs("div", { children: [_jsx("h2", { className: "text-xl font-black text-dashboard-text uppercase tracking-tight", children: "Test Email" }), _jsx("p", { className: "text-xs text-dashboard-text-secondary", children: "Send a test email to verify SMTP" })] })] }), _jsx("button", { onClick: () => !isSending && onClose(), disabled: isSending, className: "p-2 rounded-full hover:bg-dashboard-border transition-colors", children: _jsx(X, { size: 20, className: "text-dashboard-text-secondary" }) })] }), _jsx("div", { className: "p-8", children: _jsxs("div", { className: "space-y-4", children: [_jsxs("div", { children: [_jsx("label", { className: "text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest block mb-2", children: "Email Template" }), _jsxs("div", { className: "flex gap-2", children: [_jsxs("button", { onClick: () => setEmailType('test'), 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 ${emailType === 'test'
|
|
84
|
+
? 'bg-primary text-white'
|
|
85
|
+
: 'bg-dashboard-bg border border-dashboard-border text-dashboard-text hover:bg-dashboard-border'}`, children: [_jsx(Mail, { size: 14 }), "Test"] }), _jsxs("button", { onClick: () => setEmailType('welcome'), disabled: !welcomeEmailConfigured, 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 ${emailType === 'welcome'
|
|
86
|
+
? 'bg-primary text-white'
|
|
87
|
+
: welcomeEmailConfigured
|
|
88
|
+
? 'bg-dashboard-bg border border-dashboard-border text-dashboard-text hover:bg-dashboard-border'
|
|
89
|
+
: 'bg-dashboard-bg border border-dashboard-border text-dashboard-text-secondary opacity-50 cursor-not-allowed'}`, children: [_jsx(Sparkles, { size: 14 }), "Welcome", !welcomeEmailConfigured && (_jsx("span", { className: "text-[9px] normal-case opacity-70", children: "(Not configured)" }))] })] })] }), _jsxs("div", { children: [_jsx("label", { className: "text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest block mb-2", children: "Recipient Email" }), _jsx("input", { type: "email", value: email, onChange: (e) => {
|
|
90
|
+
setEmail(e.target.value);
|
|
91
|
+
setSendError(null);
|
|
92
|
+
}, onKeyDown: (e) => e.key === 'Enter' && handleSend(), placeholder: "your@email.com", 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" })] }), _jsxs("div", { children: [_jsxs("label", { className: "text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest block mb-2", children: [_jsx(Globe, { size: 12, className: "inline mr-1" }), "Language"] }), _jsx("div", { className: "flex gap-2", children: languages.map((lang) => (_jsx("button", { onClick: () => setLanguage(lang.code), className: `px-4 py-2 rounded-xl text-xs font-bold uppercase tracking-widest transition-colors ${language === lang.code
|
|
93
|
+
? 'bg-primary text-white'
|
|
94
|
+
: 'bg-dashboard-bg border border-dashboard-border text-dashboard-text hover:bg-dashboard-border'}`, children: lang.label }, lang.code))) })] }), sendError && (_jsxs("div", { className: "flex items-center gap-2 text-red-500 text-sm", children: [_jsx(AlertCircle, { size: 16 }), sendError] })), _jsx("p", { className: "text-xs text-dashboard-text-secondary", children: "A test email will be sent to this address using your configured SMTP settings." })] }) }), _jsxs("div", { className: "flex items-center justify-end gap-3 px-8 py-6 border-t border-dashboard-border bg-dashboard-bg/50", children: [_jsx("button", { onClick: onClose, disabled: isSending, 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", children: "Cancel" }), _jsx("button", { onClick: handleSend, disabled: isSending || sendSuccess, className: `inline-flex items-center gap-2 px-6 py-3 rounded-full text-[10px] font-black uppercase tracking-widest transition-colors shadow-lg ${isSending
|
|
95
|
+
? 'bg-neutral-400 text-white cursor-not-allowed'
|
|
96
|
+
: sendSuccess
|
|
97
|
+
? 'bg-green-600 text-white'
|
|
98
|
+
: 'bg-primary text-white hover:bg-primary/90'}`, children: isSending ? (_jsxs(_Fragment, { children: [_jsx(RefreshCw, { className: "w-4 h-4 animate-spin" }), "Sending..."] })) : sendSuccess ? (_jsxs(_Fragment, { children: [_jsx(CheckCircle2, { className: "w-4 h-4" }), "Sent!"] })) : (_jsxs(_Fragment, { children: [_jsx(Send, { className: "w-4 h-4" }), "Send Test"] })) })] })] })] }));
|
|
99
|
+
}
|
package/package.json
CHANGED
|
@@ -1,28 +1,36 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jhits/plugin-newsletter",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.9",
|
|
4
4
|
"description": "Newsletter management and email delivery plugin for the JHITS ecosystem",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
8
|
-
"main": "./
|
|
9
|
-
"types": "./
|
|
8
|
+
"main": "./src/index.ts",
|
|
9
|
+
"types": "./src/index.ts",
|
|
10
10
|
"exports": {
|
|
11
11
|
".": {
|
|
12
|
-
"types": "./
|
|
13
|
-
"
|
|
12
|
+
"types": "./src/index.tsx",
|
|
13
|
+
"import": "./src/index.tsx",
|
|
14
|
+
"default": "./src/index.tsx"
|
|
15
|
+
},
|
|
16
|
+
"./src": {
|
|
17
|
+
"types": "./src/index.tsx",
|
|
18
|
+
"import": "./src/index.tsx",
|
|
19
|
+
"default": "./src/index.tsx"
|
|
14
20
|
},
|
|
15
21
|
"./server": {
|
|
16
|
-
"types": "./
|
|
17
|
-
"
|
|
22
|
+
"types": "./src/index.server.ts",
|
|
23
|
+
"import": "./src/index.server.ts",
|
|
24
|
+
"default": "./src/index.server.ts"
|
|
18
25
|
}
|
|
19
26
|
},
|
|
20
27
|
"dependencies": {
|
|
28
|
+
"handlebars": "^4.7.8",
|
|
21
29
|
"lucide-react": "^0.564.0",
|
|
22
30
|
"mongodb": "^7.1.0",
|
|
23
31
|
"next-auth": "^4.24.13",
|
|
24
32
|
"nodemailer": "^8.0.1",
|
|
25
|
-
"@jhits/plugin-core": "0.0.
|
|
33
|
+
"@jhits/plugin-core": "0.0.7"
|
|
26
34
|
},
|
|
27
35
|
"peerDependencies": {
|
|
28
36
|
"next": ">=15.0.0",
|
|
@@ -30,6 +38,7 @@
|
|
|
30
38
|
"react-dom": ">=18.0.0"
|
|
31
39
|
},
|
|
32
40
|
"devDependencies": {
|
|
41
|
+
"@types/handlebars": "^4.1.0",
|
|
33
42
|
"@types/node": "^25.2.3",
|
|
34
43
|
"@types/nodemailer": "^7.0.9",
|
|
35
44
|
"@types/react": "^19.2.14",
|
|
@@ -41,7 +50,9 @@
|
|
|
41
50
|
},
|
|
42
51
|
"files": [
|
|
43
52
|
"dist",
|
|
44
|
-
"package.json"
|
|
53
|
+
"package.json",
|
|
54
|
+
"templates",
|
|
55
|
+
"data"
|
|
45
56
|
],
|
|
46
57
|
"scripts": {
|
|
47
58
|
"build": "tsc"
|
|
Binary file
|