@jhits/plugin-newsletter 0.0.8 → 0.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/data/locales/en/common.json +12 -0
  2. package/data/locales/nl/common.json +12 -0
  3. package/data/locales/sv/common.json +12 -0
  4. package/dist/api/email-utils.d.ts +6 -0
  5. package/dist/api/email-utils.d.ts.map +1 -0
  6. package/dist/api/email-utils.js +134 -0
  7. package/dist/api/handler.d.ts +2 -47
  8. package/dist/api/handler.d.ts.map +1 -1
  9. package/dist/api/handler.js +2 -523
  10. package/dist/api/handlers/index.d.ts +12 -0
  11. package/dist/api/handlers/index.d.ts.map +1 -0
  12. package/dist/api/handlers/index.js +11 -0
  13. package/dist/api/handlers/newsletters.d.ts +11 -0
  14. package/dist/api/handlers/newsletters.d.ts.map +1 -0
  15. package/dist/api/handlers/newsletters.js +250 -0
  16. package/dist/api/handlers/send-newsletter.d.ts +8 -0
  17. package/dist/api/handlers/send-newsletter.d.ts.map +1 -0
  18. package/dist/api/handlers/send-newsletter.js +206 -0
  19. package/dist/api/handlers/settings.d.ts +11 -0
  20. package/dist/api/handlers/settings.d.ts.map +1 -0
  21. package/dist/api/handlers/settings.js +304 -0
  22. package/dist/api/handlers/subscribers.d.ts +10 -0
  23. package/dist/api/handlers/subscribers.d.ts.map +1 -0
  24. package/dist/api/handlers/subscribers.js +96 -0
  25. package/dist/api/handlers/upload.d.ts +7 -0
  26. package/dist/api/handlers/upload.d.ts.map +1 -0
  27. package/dist/api/handlers/upload.js +32 -0
  28. package/dist/api/handlers/welcome-email.d.ts +13 -0
  29. package/dist/api/handlers/welcome-email.d.ts.map +1 -0
  30. package/dist/api/handlers/welcome-email.js +142 -0
  31. package/dist/api/router.d.ts.map +1 -1
  32. package/dist/api/router.js +45 -6
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +42 -16
  35. package/dist/lib/email/EmailRenderer.d.ts.map +1 -1
  36. package/dist/lib/email/EmailRenderer.js +3 -7
  37. package/dist/lib/i18n.d.ts +16 -0
  38. package/dist/lib/i18n.d.ts.map +1 -0
  39. package/dist/lib/i18n.js +60 -0
  40. package/dist/state/EditorContext.d.ts +3 -1
  41. package/dist/state/EditorContext.d.ts.map +1 -1
  42. package/dist/state/EditorContext.js +1 -5
  43. package/dist/state/reducer.js +5 -5
  44. package/dist/types/newsletter.d.ts +1 -0
  45. package/dist/types/newsletter.d.ts.map +1 -1
  46. package/dist/views/CanvasEditor/CanvasEditorView.d.ts +4 -2
  47. package/dist/views/CanvasEditor/CanvasEditorView.d.ts.map +1 -1
  48. package/dist/views/CanvasEditor/CanvasEditorView.js +73 -10
  49. package/dist/views/CanvasEditor/EditorHeader.d.ts +6 -1
  50. package/dist/views/CanvasEditor/EditorHeader.d.ts.map +1 -1
  51. package/dist/views/CanvasEditor/EditorHeader.js +66 -11
  52. package/dist/views/CanvasEditor/components/EditorSidebar.d.ts +6 -2
  53. package/dist/views/CanvasEditor/components/EditorSidebar.d.ts.map +1 -1
  54. package/dist/views/CanvasEditor/components/EditorSidebar.js +3 -3
  55. package/dist/views/CanvasEditor/hooks/useNewsletterLoader.d.ts +1 -1
  56. package/dist/views/CanvasEditor/hooks/useNewsletterLoader.d.ts.map +1 -1
  57. package/dist/views/CanvasEditor/hooks/useNewsletterLoader.js +41 -5
  58. package/dist/views/NewsletterEditor.d.ts +4 -2
  59. package/dist/views/NewsletterEditor.d.ts.map +1 -1
  60. package/dist/views/NewsletterEditor.js +2 -2
  61. package/dist/views/NewsletterManager.d.ts.map +1 -1
  62. package/dist/views/NewsletterManager.js +137 -8
  63. package/dist/views/components/SendNewsletterModal.d.ts +18 -0
  64. package/dist/views/components/SendNewsletterModal.d.ts.map +1 -0
  65. package/dist/views/components/SendNewsletterModal.js +107 -0
  66. package/dist/views/components/SmtpSettingsModal.d.ts +10 -0
  67. package/dist/views/components/SmtpSettingsModal.d.ts.map +1 -0
  68. package/dist/views/components/SmtpSettingsModal.js +145 -0
  69. package/dist/views/components/TestEmailModal.d.ts +10 -0
  70. package/dist/views/components/TestEmailModal.d.ts.map +1 -0
  71. package/dist/views/components/TestEmailModal.js +99 -0
  72. package/package.json +20 -9
  73. package/templates/logo.png +0 -0
  74. package/templates/test-email.hbs +221 -0
  75. package/templates/welcome-email-legacy.hbs +35 -0
@@ -1,11 +1,53 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
- import { useState } from 'react';
4
- import { ArrowLeft, Settings2, Save, Edit, Eye } from 'lucide-react';
3
+ import { useState, useRef, useEffect } from 'react';
4
+ import { ArrowLeft, Settings2, Save, Edit, Eye, Plus } from 'lucide-react';
5
5
  import { useEditor } from '../../state/EditorContext';
6
- export function EditorHeader({ isPreviewMode, onPreviewToggle, isSidebarOpen, onSidebarToggle, isSaving, onSave, onSaveError, isDirty = false, }) {
6
+ const ALL_LANGUAGES = [
7
+ { code: 'en', name: 'English' },
8
+ { code: 'nl', name: 'Dutch' },
9
+ { code: 'sv', name: 'Swedish' },
10
+ { code: 'de', name: 'German' },
11
+ { code: 'fr', name: 'French' },
12
+ { code: 'es', name: 'Spanish' },
13
+ { code: 'it', name: 'Italian' },
14
+ { code: 'pt', name: 'Portuguese' },
15
+ { code: 'pl', name: 'Polish' },
16
+ { code: 'ru', name: 'Russian' },
17
+ { code: 'ja', name: 'Japanese' },
18
+ { code: 'zh', name: 'Chinese' },
19
+ { code: 'ar', name: 'Arabic' },
20
+ { code: 'tr', name: 'Turkish' },
21
+ { code: 'cs', name: 'Czech' },
22
+ { code: 'da', name: 'Danish' },
23
+ { code: 'fi', name: 'Finnish' },
24
+ { code: 'el', name: 'Greek' },
25
+ { code: 'he', name: 'Hebrew' },
26
+ { code: 'hi', name: 'Hindi' },
27
+ { code: 'hu', name: 'Hungarian' },
28
+ { code: 'id', name: 'Indonesian' },
29
+ { code: 'ko', name: 'Korean' },
30
+ { code: 'no', name: 'Norwegian' },
31
+ { code: 'ro', name: 'Romanian' },
32
+ { code: 'th', name: 'Thai' },
33
+ { code: 'uk', name: 'Ukrainian' },
34
+ { code: 'vi', name: 'Vietnamese' },
35
+ ];
36
+ export function EditorHeader({ isPreviewMode, onPreviewToggle, isSidebarOpen, onSidebarToggle, isSaving, onSave, onSaveError, isDirty = false, isWelcomeEmail = false, languages = ['en'], currentLanguage = 'en', onLanguageChange, onAddLanguage, }) {
7
37
  const { state } = useEditor();
8
38
  const [saveError, setSaveError] = useState(null);
39
+ const [isAddLangOpen, setIsAddLangOpen] = useState(false);
40
+ const addLangRef = useRef(null);
41
+ useEffect(() => {
42
+ const handleClickOutside = (event) => {
43
+ if (addLangRef.current && !addLangRef.current.contains(event.target)) {
44
+ setIsAddLangOpen(false);
45
+ }
46
+ };
47
+ document.addEventListener('mousedown', handleClickOutside);
48
+ return () => document.removeEventListener('mousedown', handleClickOutside);
49
+ }, []);
50
+ const availableToAdd = ALL_LANGUAGES.filter(l => !languages.includes(l.code));
9
51
  const handleSave = async () => {
10
52
  try {
11
53
  setSaveError(null);
@@ -21,15 +63,28 @@ export function EditorHeader({ isPreviewMode, onPreviewToggle, isSidebarOpen, on
21
63
  onSaveError(errorMessage);
22
64
  }
23
65
  };
24
- return (_jsxs("header", { className: "flex items-center justify-between px-6 py-3 bg-dashboard-sidebar backdrop-blur-md border-b border-dashboard-border flex-none shrink-0", children: [_jsx("div", { className: "flex items-center gap-6", children: _jsx("button", { onClick: () => {
25
- if (isDirty) {
26
- const confirmed = window.confirm('You have unsaved changes. Are you sure you want to leave? Your changes will be lost.');
27
- if (!confirmed) {
28
- return;
66
+ const currentLangName = ALL_LANGUAGES.find(l => l.code === currentLanguage)?.name || currentLanguage.toUpperCase();
67
+ return (_jsxs("header", { className: "flex items-center justify-between px-6 py-3 bg-dashboard-sidebar backdrop-blur-md border-b border-dashboard-border flex-none shrink-0 z-50 relative", children: [_jsxs("div", { className: "flex items-center gap-6", children: [_jsx("button", { onClick: () => {
68
+ if (isDirty) {
69
+ const confirmed = window.confirm('You have unsaved changes. Are you sure you want to leave? Your changes will be lost.');
70
+ if (!confirmed) {
71
+ return;
72
+ }
29
73
  }
30
- }
31
- window.location.href = '/dashboard/newsletter';
32
- }, className: "text-neutral-500 dark:text-neutral-400 hover:text-neutral-950 dark:hover:text-white transition-colors", children: _jsx(ArrowLeft, { size: 20, strokeWidth: 1.5 }) }) }), _jsxs("div", { className: "flex items-center gap-4", children: [_jsxs("div", { className: "flex items-center bg-dashboard-bg border border-dashboard-border rounded-full p-1 gap-1", children: [_jsxs("button", { onClick: () => {
74
+ window.location.href = '/dashboard/newsletter';
75
+ }, className: "text-neutral-500 dark:text-neutral-400 hover:text-neutral-950 dark:hover:text-white transition-colors", children: _jsx(ArrowLeft, { size: 20, strokeWidth: 1.5 }) }), languages.length > 0 && (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("select", { value: currentLanguage, onChange: (e) => {
76
+ if (onLanguageChange) {
77
+ onLanguageChange(e.target.value);
78
+ }
79
+ }, className: "px-3 py-1.5 text-xs bg-dashboard-bg border border-dashboard-border rounded-lg outline-none focus:border-primary transition-all text-dashboard-text font-medium", children: languages.map((lang) => {
80
+ const langInfo = ALL_LANGUAGES.find(l => l.code === lang);
81
+ return (_jsx("option", { value: lang, children: langInfo?.name || lang.toUpperCase() }, lang));
82
+ }) }), availableToAdd.length > 0 && (_jsxs("div", { className: "relative", ref: addLangRef, children: [_jsx("button", { onClick: () => setIsAddLangOpen(!isAddLangOpen), className: "p-1.5 text-neutral-500 hover:text-primary transition-colors", title: "Add language", children: _jsx(Plus, { size: 16 }) }), isAddLangOpen && (_jsxs("div", { className: "absolute top-full left-0 mt-1 w-48 bg-dashboard-card border border-dashboard-border rounded-lg shadow-lg z-[100] py-1 max-h-60 overflow-y-auto", children: [_jsx("div", { className: "px-3 py-2 text-[10px] uppercase tracking-wider text-neutral-500 font-bold border-b border-dashboard-border", children: "Add Language" }), availableToAdd.map((lang) => (_jsx("button", { onClick: () => {
83
+ if (onAddLanguage) {
84
+ onAddLanguage(lang.code);
85
+ }
86
+ setIsAddLangOpen(false);
87
+ }, className: "w-full text-left px-3 py-2 text-xs hover:bg-dashboard-bg transition-colors text-dashboard-text", children: lang.name }, lang.code)))] }))] }))] }))] }), _jsxs("div", { className: "flex items-center gap-4", children: [_jsxs("div", { className: "flex items-center bg-dashboard-bg border border-dashboard-border rounded-full p-1 gap-1", children: [_jsxs("button", { onClick: () => {
33
88
  if (isPreviewMode) {
34
89
  onPreviewToggle();
35
90
  }
@@ -1,9 +1,13 @@
1
1
  import type { NewsletterMetadata } from '../../../types/newsletter';
2
2
  export interface EditorSidebarProps {
3
- slug: string;
4
3
  metadata: NewsletterMetadata;
5
4
  status: string;
6
5
  onMetadataUpdate: (metadata: Partial<NewsletterMetadata>) => void;
6
+ defaultLanguage?: string;
7
+ isWelcomeEmail?: boolean;
8
+ languages?: string[];
9
+ currentLanguage?: string;
10
+ onLanguageChange?: (language: string) => void;
7
11
  }
8
- export declare function EditorSidebar({ slug, metadata, status, onMetadataUpdate, }: EditorSidebarProps): import("react/jsx-runtime").JSX.Element;
12
+ export declare function EditorSidebar({ metadata, status, onMetadataUpdate, defaultLanguage, isWelcomeEmail, languages, currentLanguage, onLanguageChange, }: EditorSidebarProps): import("react/jsx-runtime").JSX.Element;
9
13
  //# sourceMappingURL=EditorSidebar.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"EditorSidebar.d.ts","sourceRoot":"","sources":["../../../../src/views/CanvasEditor/components/EditorSidebar.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAEpE,MAAM,WAAW,kBAAkB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC,kBAAkB,CAAC,KAAK,IAAI,CAAC;CACrE;AAED,wBAAgB,aAAa,CAAC,EAC1B,IAAI,EACJ,QAAQ,EACR,MAAM,EACN,gBAAgB,GACnB,EAAE,kBAAkB,2CAyIpB"}
1
+ {"version":3,"file":"EditorSidebar.d.ts","sourceRoot":"","sources":["../../../../src/views/CanvasEditor/components/EditorSidebar.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAEpE,MAAM,WAAW,kBAAkB;IAC/B,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC,kBAAkB,CAAC,KAAK,IAAI,CAAC;IAClE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;CACjD;AAED,wBAAgB,aAAa,CAAC,EAC1B,QAAQ,EACR,MAAM,EACN,gBAAgB,EAChB,eAAsB,EACtB,cAAsB,EACtB,SAAkB,EAClB,eAAsB,EACtB,gBAAgB,GACnB,EAAE,kBAAkB,2CAgHpB"}
@@ -1,8 +1,8 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { Mail, Users } from 'lucide-react';
4
- export function EditorSidebar({ slug, metadata, status, onMetadataUpdate, }) {
5
- return (_jsxs("div", { className: "p-8 w-80 min-w-0 max-w-full space-y-12 overflow-y-auto max-h-full", children: [_jsxs("section", { children: [_jsxs("div", { className: "flex items-center gap-3 mb-6", children: [_jsx(Mail, { size: 14, className: "text-neutral-500 dark:text-neutral-400" }), _jsx("label", { className: "text-[10px] uppercase tracking-[0.2em] text-neutral-500 dark:text-neutral-400 font-black", children: "Newsletter Settings" })] }), _jsxs("div", { className: "space-y-4", children: [_jsxs("div", { children: [_jsx("label", { className: "text-[10px] text-neutral-500 dark:text-neutral-400 uppercase font-bold block mb-2", children: "Email Subject" }), _jsx("input", { type: "text", value: metadata.subject || '', onChange: (e) => onMetadataUpdate({ subject: e.target.value }), placeholder: "Enter email subject", className: "w-full px-3 py-2 text-xs bg-dashboard-card border border-dashboard-border rounded-lg outline-none focus:border-primary transition-all text-dashboard-text" }), _jsxs("p", { className: "text-[9px] text-neutral-400 dark:text-neutral-500 mt-1", children: [metadata.subject?.length || 0, " / 100 characters"] })] }), _jsxs("div", { children: [_jsx("label", { className: "text-[10px] text-neutral-500 dark:text-neutral-400 uppercase font-bold block mb-2", children: "Preview Text" }), _jsx("textarea", { value: metadata.previewText || '', onChange: (e) => onMetadataUpdate({ previewText: e.target.value }), placeholder: "Preview text shown in email clients", rows: 3, className: "w-full px-3 py-2 text-xs bg-dashboard-card border border-dashboard-border rounded-lg outline-none focus:border-primary transition-all text-dashboard-text resize-none" }), _jsxs("p", { className: "text-[9px] text-neutral-400 dark:text-neutral-500 mt-1", children: [metadata.previewText?.length || 0, " / 150 characters"] })] }), _jsxs("div", { children: [_jsx("label", { className: "text-[10px] text-neutral-500 dark:text-neutral-400 uppercase font-bold block mb-2", children: "Language" }), _jsxs("select", { value: metadata.lang || 'en', onChange: (e) => onMetadataUpdate({ lang: e.target.value }), className: "w-full px-3 py-2 text-xs bg-dashboard-card border border-dashboard-border rounded-lg outline-none focus:border-primary transition-all text-dashboard-text", children: [_jsx("option", { value: "en", children: "English" }), _jsx("option", { value: "nl", children: "Dutch" }), _jsx("option", { value: "sv", children: "Swedish" })] })] })] })] }), _jsxs("section", { className: "pt-8 border-t border-neutral-200 dark:border-neutral-800", children: [_jsxs("div", { className: "flex items-center gap-3 mb-6", children: [_jsx(Users, { size: 14, className: "text-neutral-500 dark:text-neutral-400" }), _jsx("label", { className: "text-[10px] uppercase tracking-[0.2em] text-neutral-500 dark:text-neutral-400 font-black", children: "Recipients" })] }), _jsxs("div", { className: "space-y-4", children: [_jsxs("div", { children: [_jsx("label", { className: "text-[10px] text-neutral-500 dark:text-neutral-400 uppercase font-bold block mb-2", children: "Filter Type" }), _jsxs("select", { value: metadata.recipientFilter?.type || 'all', onChange: (e) => onMetadataUpdate({
4
+ export function EditorSidebar({ metadata, status, onMetadataUpdate, defaultLanguage = 'en', isWelcomeEmail = false, languages = ['en'], currentLanguage = 'en', onLanguageChange, }) {
5
+ return (_jsxs("div", { className: "p-8 w-80 min-w-0 max-w-full space-y-12 overflow-y-auto max-h-full", children: [_jsxs("section", { children: [_jsxs("div", { className: "flex items-center gap-3 mb-6", children: [_jsx(Mail, { size: 14, className: "text-neutral-500 dark:text-neutral-400" }), _jsx("label", { className: "text-[10px] uppercase tracking-[0.2em] text-neutral-500 dark:text-neutral-400 font-black", children: "Newsletter Settings" })] }), _jsxs("div", { className: "space-y-4", children: [_jsxs("div", { children: [_jsx("label", { className: "text-[10px] text-neutral-500 dark:text-neutral-400 uppercase font-bold block mb-2", children: "Email Subject" }), _jsx("input", { type: "text", value: metadata.subject || '', onChange: (e) => onMetadataUpdate({ subject: e.target.value }), placeholder: "Enter email subject", className: "w-full px-3 py-2 text-xs bg-dashboard-card border border-dashboard-border rounded-lg outline-none focus:border-primary transition-all text-dashboard-text" }), _jsxs("p", { className: "text-[9px] text-neutral-400 dark:text-neutral-500 mt-1", children: [metadata.subject?.length || 0, " / 100 characters"] })] }), _jsxs("div", { children: [_jsx("label", { className: "text-[10px] text-neutral-500 dark:text-neutral-400 uppercase font-bold block mb-2", children: "Preview Text" }), _jsx("textarea", { value: metadata.previewText || '', onChange: (e) => onMetadataUpdate({ previewText: e.target.value }), placeholder: "Preview text shown in email clients", rows: 3, className: "w-full px-3 py-2 text-xs bg-dashboard-card border border-dashboard-border rounded-lg outline-none focus:border-primary transition-all text-dashboard-text resize-none" }), _jsxs("p", { className: "text-[9px] text-neutral-400 dark:text-neutral-500 mt-1", children: [metadata.previewText?.length || 0, " / 150 characters"] })] })] })] }), _jsxs("section", { className: "pt-8 border-t border-neutral-200 dark:border-neutral-800", children: [_jsxs("div", { className: "flex items-center gap-3 mb-6", children: [_jsx(Users, { size: 14, className: "text-neutral-500 dark:text-neutral-400" }), _jsx("label", { className: "text-[10px] uppercase tracking-[0.2em] text-neutral-500 dark:text-neutral-400 font-black", children: "Recipients" })] }), _jsxs("div", { className: "space-y-4", children: [_jsxs("div", { children: [_jsx("label", { className: "text-[10px] text-neutral-500 dark:text-neutral-400 uppercase font-bold block mb-2", children: "Filter Type" }), _jsxs("select", { value: metadata.recipientFilter?.type || 'all', onChange: (e) => onMetadataUpdate({
6
6
  recipientFilter: {
7
7
  type: e.target.value,
8
8
  value: metadata.recipientFilter?.value,
@@ -12,5 +12,5 @@ export function EditorSidebar({ slug, metadata, status, onMetadataUpdate, }) {
12
12
  type: 'language',
13
13
  value: e.target.value,
14
14
  }
15
- }), className: "w-full px-3 py-2 text-xs bg-dashboard-card border border-dashboard-border rounded-lg outline-none focus:border-primary transition-all text-dashboard-text", children: [_jsx("option", { value: "en", children: "English" }), _jsx("option", { value: "nl", children: "Dutch" }), _jsx("option", { value: "sv", children: "Swedish" })] })] }))] })] }), _jsx("section", { className: "pt-8 border-t border-neutral-200 dark:border-neutral-800", children: _jsxs("div", { className: "space-y-3", children: [_jsxs("div", { children: [_jsx("label", { className: "text-[10px] text-neutral-500 dark:text-neutral-400 uppercase font-bold block mb-2", children: "Slug" }), _jsx("input", { type: "text", value: slug, readOnly: true, className: "w-full px-3 py-2 text-xs bg-dashboard-bg border border-dashboard-border rounded-lg text-dashboard-text opacity-60 cursor-not-allowed" })] }), _jsxs("div", { children: [_jsx("label", { className: "text-[10px] text-neutral-500 dark:text-neutral-400 uppercase font-bold block mb-2", children: "Status" }), _jsx("div", { className: "px-3 py-2 text-xs bg-dashboard-bg border border-dashboard-border rounded-lg", children: _jsx("span", { className: "uppercase font-bold", children: status }) })] })] }) })] }));
15
+ }), className: "w-full px-3 py-2 text-xs bg-dashboard-card border border-dashboard-border rounded-lg outline-none focus:border-primary transition-all text-dashboard-text", children: [_jsx("option", { value: "en", children: "English" }), _jsx("option", { value: "nl", children: "Dutch" }), _jsx("option", { value: "sv", children: "Swedish" })] })] }))] })] }), _jsx("section", { className: "pt-8 border-t border-neutral-200 dark:border-neutral-800", children: _jsx("div", { className: "space-y-3", children: _jsxs("div", { children: [_jsx("label", { className: "text-[10px] text-neutral-500 dark:text-neutral-400 uppercase font-bold block mb-2", children: "Status" }), _jsx("div", { className: "px-3 py-2 text-xs bg-dashboard-bg border border-dashboard-border rounded-lg", children: _jsx("span", { className: "uppercase font-bold", children: status }) })] }) }) })] }));
16
16
  }
@@ -1,5 +1,5 @@
1
1
  import { Newsletter } from '../../../types/newsletter';
2
- export declare function useNewsletterLoader(newsletterSlug: string | undefined, currentNewsletterId: string | null, loadNewsletter: (newsletter: Newsletter) => void): {
2
+ export declare function useNewsletterLoader(newsletterId: string | undefined, currentNewsletterId: string | null, loadNewsletter: (newsletter: Newsletter) => void, isWelcomeEmail?: boolean, language?: string): {
3
3
  isLoadingNewsletter: boolean;
4
4
  };
5
5
  //# sourceMappingURL=useNewsletterLoader.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useNewsletterLoader.d.ts","sourceRoot":"","sources":["../../../../src/views/CanvasEditor/hooks/useNewsletterLoader.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAEvD,wBAAgB,mBAAmB,CAC/B,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,mBAAmB,EAAE,MAAM,GAAG,IAAI,EAClC,cAAc,EAAE,CAAC,UAAU,EAAE,UAAU,KAAK,IAAI;;EA2BnD"}
1
+ {"version":3,"file":"useNewsletterLoader.d.ts","sourceRoot":"","sources":["../../../../src/views/CanvasEditor/hooks/useNewsletterLoader.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAEvD,wBAAgB,mBAAmB,CAC/B,YAAY,EAAE,MAAM,GAAG,SAAS,EAChC,mBAAmB,EAAE,MAAM,GAAG,IAAI,EAClC,cAAc,EAAE,CAAC,UAAU,EAAE,UAAU,KAAK,IAAI,EAChD,cAAc,CAAC,EAAE,OAAO,EACxB,QAAQ,CAAC,EAAE,MAAM;;EAgEpB"}
@@ -1,16 +1,52 @@
1
- import { useEffect, useState } from 'react';
2
- export function useNewsletterLoader(newsletterSlug, currentNewsletterId, loadNewsletter) {
1
+ import { useEffect, useState, useRef } from 'react';
2
+ export function useNewsletterLoader(newsletterId, currentNewsletterId, loadNewsletter, isWelcomeEmail, language) {
3
3
  const [isLoadingNewsletter, setIsLoadingNewsletter] = useState(false);
4
+ const hasLoadedRef = useRef(false);
4
5
  useEffect(() => {
5
- if (newsletterSlug && !currentNewsletterId) {
6
+ // Prevent double loading
7
+ if (hasLoadedRef.current) {
8
+ return;
9
+ }
10
+ // For welcome emails, wait until language is provided
11
+ if (isWelcomeEmail && !language) {
12
+ return;
13
+ }
14
+ // Skip if we have a regular newsletter id but no id yet, or if this is welcome email mode
15
+ if (isWelcomeEmail) {
16
+ // Load welcome email with language
17
+ // Load welcome email with language
18
+ const loadWelcomeEmail = async () => {
19
+ try {
20
+ setIsLoadingNewsletter(true);
21
+ const langParam = language ? `?language=${language}` : '';
22
+ const response = await fetch(`/api/plugin-newsletter/welcome-email${langParam}`);
23
+ if (!response.ok) {
24
+ throw new Error('Failed to load welcome email');
25
+ }
26
+ const newsletter = await response.json();
27
+ hasLoadedRef.current = true;
28
+ loadNewsletter(newsletter);
29
+ }
30
+ catch (error) {
31
+ console.error('Failed to load welcome email:', error);
32
+ }
33
+ finally {
34
+ setIsLoadingNewsletter(false);
35
+ }
36
+ };
37
+ loadWelcomeEmail();
38
+ return;
39
+ }
40
+ if (newsletterId && !currentNewsletterId) {
6
41
  const loadNewsletterData = async () => {
7
42
  try {
8
43
  setIsLoadingNewsletter(true);
9
- const response = await fetch(`/api/plugin-newsletter/newsletters/${newsletterSlug}`);
44
+ const response = await fetch(`/api/plugin-newsletter/newsletters/${newsletterId}`);
10
45
  if (!response.ok) {
11
46
  throw new Error('Failed to load newsletter');
12
47
  }
13
48
  const newsletter = await response.json();
49
+ hasLoadedRef.current = true;
14
50
  loadNewsletter(newsletter);
15
51
  }
16
52
  catch (error) {
@@ -23,6 +59,6 @@ export function useNewsletterLoader(newsletterSlug, currentNewsletterId, loadNew
23
59
  };
24
60
  loadNewsletterData();
25
61
  }
26
- }, [newsletterSlug, currentNewsletterId, loadNewsletter]);
62
+ }, [newsletterId, currentNewsletterId, isWelcomeEmail, language]);
27
63
  return { isLoadingNewsletter };
28
64
  }
@@ -3,7 +3,7 @@
3
3
  * Block-based editor for creating and editing newsletters
4
4
  */
5
5
  export interface NewsletterEditorViewProps {
6
- newsletterSlug?: string;
6
+ newsletterId?: string;
7
7
  siteId: string;
8
8
  locale: string;
9
9
  darkMode?: boolean;
@@ -11,6 +11,8 @@ export interface NewsletterEditorViewProps {
11
11
  light: string;
12
12
  dark?: string;
13
13
  };
14
+ /** If true, this editor is for the welcome email */
15
+ isWelcomeEmail?: boolean;
14
16
  }
15
- export declare function NewsletterEditorView({ newsletterSlug, siteId, locale, darkMode, backgroundColors }: NewsletterEditorViewProps): import("react/jsx-runtime").JSX.Element;
17
+ export declare function NewsletterEditorView({ newsletterId, siteId, locale, darkMode, backgroundColors, isWelcomeEmail }: NewsletterEditorViewProps): import("react/jsx-runtime").JSX.Element;
16
18
  //# sourceMappingURL=NewsletterEditor.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"NewsletterEditor.d.ts","sourceRoot":"","sources":["../../src/views/NewsletterEditor.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAOH,MAAM,WAAW,yBAAyB;IACtC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,gBAAgB,CAAC,EAAE;QACf,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;CACL;AAED,wBAAgB,oBAAoB,CAAC,EACjC,cAAc,EACd,MAAM,EACN,MAAM,EACN,QAAQ,EACR,gBAAgB,EACnB,EAAE,yBAAyB,2CAU3B"}
1
+ {"version":3,"file":"NewsletterEditor.d.ts","sourceRoot":"","sources":["../../src/views/NewsletterEditor.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAOH,MAAM,WAAW,yBAAyB;IACtC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,gBAAgB,CAAC,EAAE;QACf,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,oDAAoD;IACpD,cAAc,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,wBAAgB,oBAAoB,CAAC,EACjC,YAAY,EACZ,MAAM,EACN,MAAM,EACN,QAAQ,EACR,gBAAgB,EAChB,cAAc,EACjB,EAAE,yBAAyB,2CAW3B"}
@@ -5,6 +5,6 @@
5
5
  'use client';
6
6
  import { jsx as _jsx } from "react/jsx-runtime";
7
7
  import { CanvasEditorView } from './CanvasEditor';
8
- export function NewsletterEditorView({ newsletterSlug, siteId, locale, darkMode, backgroundColors }) {
9
- return (_jsx(CanvasEditorView, { newsletterSlug: newsletterSlug, siteId: siteId, locale: locale, darkMode: darkMode, backgroundColors: backgroundColors }));
8
+ export function NewsletterEditorView({ newsletterId, siteId, locale, darkMode, backgroundColors, isWelcomeEmail }) {
9
+ return (_jsx(CanvasEditorView, { newsletterId: newsletterId, siteId: siteId, locale: locale, darkMode: darkMode, backgroundColors: backgroundColors, isWelcomeEmail: isWelcomeEmail }));
10
10
  }
@@ -1 +1 @@
1
- {"version":3,"file":"NewsletterManager.d.ts","sourceRoot":"","sources":["../../src/views/NewsletterManager.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAQH,MAAM,WAAW,0BAA0B;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAClB;AAiBD,wBAAgB,qBAAqB,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,0BAA0B,2CAgNnF"}
1
+ {"version":3,"file":"NewsletterManager.d.ts","sourceRoot":"","sources":["../../src/views/NewsletterManager.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAWH,MAAM,WAAW,0BAA0B;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAClB;AAiBD,wBAAgB,qBAAqB,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,0BAA0B,2CAgcnF"}
@@ -3,9 +3,12 @@
3
3
  * List and manage newsletters
4
4
  */
5
5
  'use client';
6
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
6
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
7
7
  import { useState, useEffect } from 'react';
8
- import { Plus, Mail, Calendar, Trash2, Edit2 } from 'lucide-react';
8
+ import { Plus, Mail, Calendar, Trash2, Edit2, Settings2, Sparkles, CheckCircle2, Clock, Send, Users } from 'lucide-react';
9
+ import { SmtpSettingsModal } from './components/SmtpSettingsModal';
10
+ import { TestEmailModal } from './components/TestEmailModal';
11
+ import { SendNewsletterModal } from './components/SendNewsletterModal';
9
12
  function getStatusBadgeColor(status) {
10
13
  switch (status) {
11
14
  case 'sent':
@@ -24,6 +27,15 @@ export function NewsletterManagerView({ siteId, locale }) {
24
27
  const [newsletters, setNewsletters] = useState([]);
25
28
  const [isLoading, setIsLoading] = useState(true);
26
29
  const [statusFilter, setStatusFilter] = useState('all');
30
+ const [showSmtpModal, setShowSmtpModal] = useState(false);
31
+ const [showTestEmailModal, setShowTestEmailModal] = useState(false);
32
+ const [showSendModal, setShowSendModal] = useState(false);
33
+ const [selectedNewsletter, setSelectedNewsletter] = useState(null);
34
+ const [smtpStatus, setSmtpStatus] = useState('loading');
35
+ // Welcome Email Status
36
+ const [welcomeEmailStatus, setWelcomeEmailStatus] = useState('not_configured');
37
+ const [welcomeEmailLastUpdated, setWelcomeEmailLastUpdated] = useState(null);
38
+ const [subscriberCount, setSubscriberCount] = useState(0);
27
39
  // Fetch newsletters
28
40
  useEffect(() => {
29
41
  const fetchNewsletters = async () => {
@@ -47,6 +59,65 @@ export function NewsletterManagerView({ siteId, locale }) {
47
59
  };
48
60
  fetchNewsletters();
49
61
  }, []);
62
+ // Fetch welcome email status
63
+ useEffect(() => {
64
+ const fetchWelcomeEmailStatus = async () => {
65
+ try {
66
+ const response = await fetch('/api/plugin-newsletter/welcome-email/status', {
67
+ credentials: 'include',
68
+ });
69
+ if (response.ok) {
70
+ const data = await response.json();
71
+ setWelcomeEmailStatus(data.configured ? 'configured' : 'not_configured');
72
+ setWelcomeEmailLastUpdated(data.lastUpdated || null);
73
+ }
74
+ }
75
+ catch (error) {
76
+ console.error('Failed to load welcome email status:', error);
77
+ }
78
+ };
79
+ fetchWelcomeEmailStatus();
80
+ }, []);
81
+ // Fetch SMTP status
82
+ useEffect(() => {
83
+ const fetchSmtpStatus = async () => {
84
+ try {
85
+ const response = await fetch('/api/plugin-newsletter/smtp', {
86
+ credentials: 'include',
87
+ });
88
+ if (response.ok) {
89
+ const data = await response.json();
90
+ setSmtpStatus(data.host ? 'configured' : 'not_configured');
91
+ }
92
+ else {
93
+ setSmtpStatus('not_configured');
94
+ }
95
+ }
96
+ catch (error) {
97
+ console.error('Failed to load SMTP status:', error);
98
+ setSmtpStatus('not_configured');
99
+ }
100
+ };
101
+ fetchSmtpStatus();
102
+ }, []);
103
+ // Fetch subscriber count
104
+ useEffect(() => {
105
+ const fetchSubscriberCount = async () => {
106
+ try {
107
+ const response = await fetch('/api/plugin-newsletter/subscribers', {
108
+ credentials: 'include',
109
+ });
110
+ if (response.ok) {
111
+ const data = await response.json();
112
+ setSubscriberCount(Array.isArray(data) ? data.length : 0);
113
+ }
114
+ }
115
+ catch (error) {
116
+ console.error('Failed to load subscriber count:', error);
117
+ }
118
+ };
119
+ fetchSubscriberCount();
120
+ }, []);
50
121
  // Filter newsletters
51
122
  const filteredNewsletters = statusFilter === 'all'
52
123
  ? newsletters
@@ -56,16 +127,55 @@ export function NewsletterManagerView({ siteId, locale }) {
56
127
  window.location.href = '/dashboard/newsletter/new';
57
128
  };
58
129
  // Handle edit newsletter
59
- const handleEdit = (slug) => {
60
- window.location.href = `/dashboard/newsletter/editor/${slug}`;
130
+ const handleEdit = (id) => {
131
+ if (!id) {
132
+ alert('Cannot edit: newsletter id is missing');
133
+ return;
134
+ }
135
+ window.location.href = `/dashboard/newsletter/editor/${id}`;
136
+ };
137
+ // Handle send newsletter
138
+ const handleSend = async (newsletter) => {
139
+ if (smtpStatus === 'not_configured') {
140
+ alert('Please configure SMTP settings first');
141
+ setShowSmtpModal(true);
142
+ return;
143
+ }
144
+ if (newsletter.status === 'sent') {
145
+ alert('This newsletter has already been sent');
146
+ return;
147
+ }
148
+ try {
149
+ const response = await fetch(`/api/plugin-newsletter/newsletters/${newsletter.id}/send`, {
150
+ credentials: 'include',
151
+ });
152
+ const data = await response.json();
153
+ if (!response.ok) {
154
+ alert(data.error || 'Failed to load newsletter details');
155
+ return;
156
+ }
157
+ setSelectedNewsletter({
158
+ ...newsletter,
159
+ hasContent: data.hasContent,
160
+ });
161
+ setShowSendModal(true);
162
+ }
163
+ catch (error) {
164
+ console.error('Failed to load newsletter:', error);
165
+ alert('Failed to load newsletter details');
166
+ }
61
167
  };
62
168
  // Handle delete newsletter
63
- const handleDelete = async (slug, title) => {
169
+ const handleDelete = async (id, title) => {
170
+ if (!id) {
171
+ alert('Cannot delete: newsletter id is missing');
172
+ return;
173
+ }
64
174
  if (!confirm(`Are you sure you want to delete "${title}"?`)) {
65
175
  return;
66
176
  }
67
177
  try {
68
- const response = await fetch(`/api/plugin-newsletter/newsletters/${slug}`, {
178
+ const response = await fetch(`/api/plugin-newsletter/newsletters/${id}`, {
69
179
  method: 'DELETE',
70
180
  credentials: 'include',
71
181
  });
@@ -73,7 +183,7 @@ export function NewsletterManagerView({ siteId, locale }) {
73
183
  throw new Error('Failed to delete newsletter');
74
184
  }
75
185
  // Remove from local state
76
- setNewsletters(prev => prev.filter(n => n.slug !== slug));
186
+ setNewsletters(prev => prev.filter(n => n.id !== id));
77
187
  }
78
188
  catch (error) {
79
189
  console.error('Failed to delete newsletter:', error);
@@ -91,5 +201,24 @@ export function NewsletterManagerView({ siteId, locale }) {
91
201
  year: 'numeric',
92
202
  });
93
203
  };
94
- return (_jsx("div", { className: "h-full w-full rounded-[2.5rem] bg-white dark:bg-neutral-900 p-8 overflow-y-auto", children: _jsxs("div", { className: "max-w-7xl mx-auto", children: [_jsxs("div", { className: "flex flex-col md:flex-row md:items-center justify-between gap-6 mb-8", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-3xl font-black text-dashboard-text uppercase tracking-tighter mb-2", children: "Newsletters" }), _jsx("p", { className: "text-sm text-dashboard-text-secondary", children: "Create and manage your email newsletters" })] }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsxs("select", { value: statusFilter, onChange: (e) => setStatusFilter(e.target.value), className: "bg-dashboard-bg border border-dashboard-border rounded-xl px-4 py-2.5 text-xs font-bold text-dashboard-text outline-none cursor-pointer uppercase tracking-widest", children: [_jsx("option", { value: "all", children: "All Status" }), _jsx("option", { value: "draft", children: "Draft" }), _jsx("option", { value: "scheduled", children: "Scheduled" }), _jsx("option", { value: "sent", children: "Sent" }), _jsx("option", { value: "archived", children: "Archived" })] }), _jsxs("button", { onClick: handleCreate, className: "inline-flex items-center gap-2 px-6 py-3 rounded-full text-[10px] font-black uppercase tracking-widest transition-colors shadow-lg shadow-primary/20 bg-primary text-white hover:bg-primary/90", children: [_jsx(Plus, { size: 14 }), "New Newsletter"] })] })] }), _jsx("div", { className: "bg-dashboard-bg rounded-3xl border border-dashboard-border overflow-hidden", children: isLoading ? (_jsx("div", { className: "flex items-center justify-center py-20", children: _jsx("div", { className: "w-8 h-8 border-4 border-primary/20 border-t-primary rounded-full animate-spin" }) })) : filteredNewsletters.length === 0 ? (_jsxs("div", { className: "py-24 text-center", children: [_jsx(Mail, { size: 64, className: "mx-auto text-dashboard-text-secondary mb-4" }), _jsx("p", { className: "text-dashboard-text-secondary font-serif italic text-lg mb-6", children: statusFilter === 'all' ? 'No newsletters yet.' : `No newsletters found with status "${statusFilter}".` }), _jsxs("button", { onClick: handleCreate, className: "inline-flex items-center gap-2 px-6 py-3 rounded-full text-[10px] font-black uppercase tracking-widest transition-colors shadow-lg shadow-primary/20 bg-primary text-white hover:bg-primary/90", children: [_jsx(Plus, { size: 14 }), "Create Your First Newsletter"] })] })) : (_jsx("div", { className: "overflow-x-auto", children: _jsxs("table", { className: "w-full text-left border-collapse", children: [_jsx("thead", { children: _jsxs("tr", { className: "bg-dashboard-bg text-dashboard-text text-[10px] uppercase tracking-[0.2em] font-black border-b border-dashboard-border", children: [_jsx("th", { className: "px-8 py-5", children: "Title" }), _jsx("th", { className: "px-8 py-5", children: "Subject" }), _jsx("th", { className: "px-8 py-5", children: "Status" }), _jsx("th", { className: "px-8 py-5 text-right", children: "Updated" }), _jsx("th", { className: "px-8 py-5 text-right", children: "Actions" })] }) }), _jsx("tbody", { className: "divide-y divide-dashboard-border", children: filteredNewsletters.map((newsletter) => (_jsxs("tr", { className: "hover:bg-dashboard-bg transition-colors group", children: [_jsx("td", { className: "px-8 py-5", children: _jsxs("div", { className: "flex items-center gap-4", children: [_jsx("div", { className: "w-10 h-10 rounded-full bg-primary/10 flex items-center justify-center text-primary group-hover:bg-primary group-hover:text-white transition-colors", children: _jsx(Mail, { size: 18 }) }), _jsx("span", { className: "text-sm font-medium text-dashboard-text tracking-tight", children: newsletter.title })] }) }), _jsx("td", { className: "px-8 py-5", children: _jsx("span", { className: "text-sm text-dashboard-text-secondary", children: newsletter.subject || 'No subject' }) }), _jsx("td", { className: "px-8 py-5", children: _jsx("span", { className: `text-[10px] font-black px-3 py-1 rounded-full uppercase border ${getStatusBadgeColor(newsletter.status)}`, children: newsletter.status }) }), _jsx("td", { className: "px-8 py-5 text-right text-xs text-dashboard-text-secondary font-medium", children: _jsxs("div", { className: "flex items-center justify-end gap-2", children: [_jsx(Calendar, { size: 14 }), formatDate(newsletter.updatedAt)] }) }), _jsx("td", { className: "px-8 py-5 text-right", children: _jsxs("div", { className: "flex items-center justify-end gap-2", children: [_jsx("button", { onClick: () => handleEdit(newsletter.slug), className: "p-2.5 rounded-full text-dashboard-text-secondary hover:text-primary hover:bg-primary/10 transition-colors", title: "Edit newsletter", children: _jsx(Edit2, { size: 18 }) }), _jsx("button", { onClick: () => handleDelete(newsletter.slug, newsletter.title), className: "p-2.5 rounded-full text-dashboard-text-secondary hover:text-red-500 hover:bg-red-50 dark:hover:bg-red-900/20 transition-colors", title: "Delete newsletter", children: _jsx(Trash2, { size: 18 }) })] }) })] }, newsletter.id))) })] }) })) })] }) }));
204
+ return (_jsxs(_Fragment, { children: [_jsx("div", { className: "h-full w-full rounded-[2.5rem] bg-white dark:bg-neutral-900 p-8 overflow-y-auto", children: _jsxs("div", { className: "max-w-7xl mx-auto", children: [_jsxs("div", { className: "flex flex-col md:flex-row md:items-center justify-between gap-6 mb-8", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-3xl font-black text-dashboard-text uppercase tracking-tighter mb-2", children: "Newsletters" }), _jsx("p", { className: "text-sm text-dashboard-text-secondary", children: "Create and manage your email newsletters" })] }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsxs("button", { onClick: () => window.location.href = '/dashboard/newsletter/subscribers', className: "inline-flex items-center gap-2 px-4 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: [_jsx(Users, { size: 14 }), "Subscribers"] }), _jsxs("button", { onClick: () => {
205
+ if (smtpStatus === 'not_configured') {
206
+ alert('Please configure SMTP settings first');
207
+ setShowSmtpModal(true);
208
+ }
209
+ else {
210
+ setShowTestEmailModal(true);
211
+ }
212
+ }, className: "inline-flex items-center gap-2 px-4 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: [_jsx(Send, { size: 14 }), "Test Email"] }), _jsxs("button", { onClick: () => setShowSmtpModal(true), className: `inline-flex items-center gap-2 px-4 py-3 rounded-full text-[10px] font-black uppercase tracking-widest transition-colors ${smtpStatus === 'not_configured'
213
+ ? 'bg-amber-50 border-amber-300 text-amber-700 dark:bg-amber-900/20 dark:border-amber-700 dark:text-amber-400 hover:bg-amber-100 dark:hover:bg-amber-900/40'
214
+ : 'bg-dashboard-bg border border-dashboard-border text-dashboard-text hover:bg-dashboard-border'}`, children: [_jsx(Settings2, { size: 14 }), "SMTP", smtpStatus === 'not_configured' && (_jsx("span", { className: "w-2 h-2 rounded-full bg-amber-500" }))] }), _jsxs("select", { value: statusFilter, onChange: (e) => setStatusFilter(e.target.value), className: "bg-dashboard-bg border border-dashboard-border rounded-xl px-4 py-2.5 text-xs font-bold text-dashboard-text outline-none cursor-pointer uppercase tracking-widest", children: [_jsx("option", { value: "all", children: "All Status" }), _jsx("option", { value: "draft", children: "Draft" }), _jsx("option", { value: "scheduled", children: "Scheduled" }), _jsx("option", { value: "sent", children: "Sent" }), _jsx("option", { value: "archived", children: "Archived" })] }), _jsxs("button", { onClick: handleCreate, className: "inline-flex items-center gap-2 px-6 py-3 rounded-full text-[10px] font-black uppercase tracking-widest transition-colors shadow-lg shadow-primary/20 bg-primary text-white hover:bg-primary/90", children: [_jsx(Plus, { size: 14 }), "New Newsletter"] })] })] }), _jsx("div", { className: "mb-6 p-6 rounded-2xl bg-gradient-to-br from-primary/5 via-primary/10 to-transparent border border-dashboard-border", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-5", children: [_jsx("div", { className: "w-12 h-12 rounded-2xl bg-primary/10 flex items-center justify-center", children: _jsx(Sparkles, { className: "w-6 h-6 text-primary" }) }), _jsxs("div", { children: [_jsx("h3", { className: "text-sm font-black text-dashboard-text uppercase tracking-tight mb-1", children: "Welcome Email" }), _jsx("p", { className: "text-xs text-dashboard-text-secondary mb-2", children: "The email sent automatically when someone subscribes" }), _jsxs("div", { className: "flex items-center gap-3 flex-wrap", children: [welcomeEmailStatus === 'configured' ? (_jsxs("span", { className: "inline-flex items-center gap-1.5 text-[10px] font-bold uppercase tracking-widest text-green-600 dark:text-green-400", children: [_jsx(CheckCircle2, { size: 12 }), "Configured"] })) : (_jsxs("span", { className: "inline-flex items-center gap-1.5 text-[10px] font-bold uppercase tracking-widest text-amber-600 dark:text-amber-400", children: [_jsx(Clock, { size: 12 }), "Not configured"] })), _jsxs("button", { onClick: () => window.location.href = '/dashboard/newsletter/subscribers', className: "inline-flex items-center gap-1.5 text-[10px] font-bold uppercase tracking-widest text-primary hover:underline", children: [_jsx(Users, { size: 12 }), subscriberCount, " subscriber", subscriberCount !== 1 ? 's' : ''] }), welcomeEmailLastUpdated && (_jsxs("span", { className: "text-[10px] text-dashboard-text-secondary", children: ["Last updated: ", new Date(welcomeEmailLastUpdated).toLocaleDateString()] }))] })] })] }), _jsxs("button", { onClick: () => window.location.href = '/dashboard/newsletter/welcome', className: "inline-flex items-center gap-2 px-4 py-2.5 rounded-full text-[10px] font-black uppercase tracking-widest transition-colors bg-primary text-white hover:bg-primary/90", children: [_jsx(Edit2, { size: 14 }), welcomeEmailStatus === 'configured' ? 'Edit' : 'Configure'] })] }) }), _jsx("div", { className: "bg-dashboard-bg rounded-3xl border border-dashboard-border overflow-hidden", children: isLoading ? (_jsx("div", { className: "flex items-center justify-center py-20", children: _jsx("div", { className: "w-8 h-8 border-4 border-primary/20 border-t-primary rounded-full animate-spin" }) })) : filteredNewsletters.length === 0 ? (_jsxs("div", { className: "py-24 text-center", children: [_jsx(Mail, { size: 64, className: "mx-auto text-dashboard-text-secondary mb-4" }), _jsx("p", { className: "text-dashboard-text-secondary font-serif italic text-lg mb-6", children: statusFilter === 'all' ? 'No newsletters yet.' : `No newsletters found with status "${statusFilter}".` }), _jsxs("button", { onClick: handleCreate, className: "inline-flex items-center gap-2 px-6 py-3 rounded-full text-[10px] font-black uppercase tracking-widest transition-colors shadow-lg shadow-primary/20 bg-primary text-white hover:bg-primary/90", children: [_jsx(Plus, { size: 14 }), "Create Your First Newsletter"] })] })) : (_jsx("div", { className: "overflow-x-auto", children: _jsxs("table", { className: "w-full text-left border-collapse", children: [_jsx("thead", { children: _jsxs("tr", { className: "bg-dashboard-bg text-dashboard-text text-[10px] uppercase tracking-[0.2em] font-black border-b border-dashboard-border", children: [_jsx("th", { className: "px-8 py-5", children: "Title" }), _jsx("th", { className: "px-8 py-5", children: "Subject" }), _jsx("th", { className: "px-8 py-5", children: "Status" }), _jsx("th", { className: "px-8 py-5 text-right", children: "Updated" }), _jsx("th", { className: "px-8 py-5 text-right", children: "Actions" })] }) }), _jsx("tbody", { className: "divide-y divide-dashboard-border", children: filteredNewsletters.map((newsletter) => (_jsxs("tr", { className: "hover:bg-dashboard-bg transition-colors group", children: [_jsx("td", { className: "px-8 py-5", children: _jsxs("div", { className: "flex items-center gap-4", children: [_jsx("div", { className: "w-10 h-10 rounded-full bg-primary/10 flex items-center justify-center text-primary group-hover:bg-primary group-hover:text-white transition-colors", children: _jsx(Mail, { size: 18 }) }), _jsx("span", { className: "text-sm font-medium text-dashboard-text tracking-tight", children: newsletter.title })] }) }), _jsx("td", { className: "px-8 py-5", children: _jsx("span", { className: "text-sm text-dashboard-text-secondary", children: newsletter.subject || 'No subject' }) }), _jsx("td", { className: "px-8 py-5", children: _jsx("span", { className: `text-[10px] font-black px-3 py-1 rounded-full uppercase border ${getStatusBadgeColor(newsletter.status)}`, children: newsletter.status }) }), _jsx("td", { className: "px-8 py-5 text-right text-xs text-dashboard-text-secondary font-medium", children: _jsxs("div", { className: "flex items-center justify-end gap-2", children: [_jsx(Calendar, { size: 14 }), formatDate(newsletter.updatedAt)] }) }), _jsx("td", { className: "px-8 py-5 text-right", children: _jsxs("div", { className: "flex items-center justify-end gap-2", children: [newsletter.status !== 'sent' && (_jsx("button", { onClick: () => handleSend(newsletter), className: "p-2.5 rounded-full text-dashboard-text-secondary hover:text-primary hover:bg-primary/10 transition-colors", title: "Send newsletter", children: _jsx(Send, { size: 18 }) })), _jsx("button", { onClick: () => handleEdit(newsletter.id), className: "p-2.5 rounded-full text-dashboard-text-secondary hover:text-primary hover:bg-primary/10 transition-colors", title: "Edit newsletter", children: _jsx(Edit2, { size: 18 }) }), _jsx("button", { onClick: () => handleDelete(newsletter.id, newsletter.title), className: "p-2.5 rounded-full text-dashboard-text-secondary hover:text-red-500 hover:bg-red-50 dark:hover:bg-red-900/20 transition-colors", title: "Delete newsletter", children: _jsx(Trash2, { size: 18 }) })] }) })] }, newsletter.id))) })] }) })) })] }) }), _jsx(SmtpSettingsModal, { isOpen: showSmtpModal, onClose: () => setShowSmtpModal(false) }), _jsx(TestEmailModal, { isOpen: showTestEmailModal, onClose: () => setShowTestEmailModal(false) }), selectedNewsletter && (_jsx(SendNewsletterModal, { isOpen: showSendModal, onClose: () => {
215
+ setShowSendModal(false);
216
+ setSelectedNewsletter(null);
217
+ }, newsletter: {
218
+ id: selectedNewsletter.id,
219
+ title: selectedNewsletter.title,
220
+ subject: selectedNewsletter.subject,
221
+ status: selectedNewsletter.status,
222
+ hasContent: selectedNewsletter.hasContent,
223
+ }, subscriberCount: subscriberCount }))] }));
95
224
  }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Send Newsletter Modal Component
3
+ */
4
+ interface SendNewsletterModalProps {
5
+ isOpen: boolean;
6
+ onClose: () => void;
7
+ newsletter: {
8
+ id: string;
9
+ title: string;
10
+ subject: string;
11
+ status: string;
12
+ hasContent: boolean;
13
+ };
14
+ subscriberCount: number;
15
+ }
16
+ export declare function SendNewsletterModal({ isOpen, onClose, newsletter, subscriberCount }: SendNewsletterModalProps): import("react/jsx-runtime").JSX.Element | null;
17
+ export {};
18
+ //# sourceMappingURL=SendNewsletterModal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SendNewsletterModal.d.ts","sourceRoot":"","sources":["../../../src/views/components/SendNewsletterModal.tsx"],"names":[],"mappings":"AAAA;;GAEG;AAOH,UAAU,wBAAwB;IAC9B,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,UAAU,EAAE;QACR,EAAE,EAAE,MAAM,CAAC;QACX,KAAK,EAAE,MAAM,CAAC;QACd,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,MAAM,CAAC;QACf,UAAU,EAAE,OAAO,CAAC;KACvB,CAAC;IACF,eAAe,EAAE,MAAM,CAAC;CAC3B;AAWD,wBAAgB,mBAAmB,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,EAAE,wBAAwB,kDAkS7G"}