@jhits/plugin-newsletter 0.0.4 → 0.0.6

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 (173) hide show
  1. package/dist/api/handler.d.ts +51 -0
  2. package/dist/api/handler.d.ts.map +1 -0
  3. package/dist/api/handler.js +526 -0
  4. package/dist/api/router.d.ts +11 -0
  5. package/dist/api/router.d.ts.map +1 -0
  6. package/dist/api/router.js +82 -0
  7. package/dist/index.d.ts +46 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +222 -0
  10. package/dist/index.server.d.ts +10 -0
  11. package/dist/index.server.d.ts.map +1 -0
  12. package/dist/index.server.js +8 -0
  13. package/dist/init.d.ts +49 -0
  14. package/dist/init.d.ts.map +1 -0
  15. package/dist/init.js +42 -0
  16. package/dist/lib/blocks/BlockRenderer.d.ts +43 -0
  17. package/dist/lib/blocks/BlockRenderer.d.ts.map +1 -0
  18. package/dist/lib/blocks/BlockRenderer.js +48 -0
  19. package/dist/lib/email/EmailRenderer.d.ts +47 -0
  20. package/dist/lib/email/EmailRenderer.d.ts.map +1 -0
  21. package/dist/lib/email/EmailRenderer.js +359 -0
  22. package/dist/lib/email/index.d.ts +6 -0
  23. package/dist/lib/email/index.d.ts.map +1 -0
  24. package/dist/lib/email/index.js +4 -0
  25. package/dist/lib/mappers/apiMapper.d.ts +30 -0
  26. package/dist/lib/mappers/apiMapper.d.ts.map +1 -0
  27. package/dist/lib/mappers/apiMapper.js +36 -0
  28. package/dist/lib/utils/blockHelpers.d.ts +23 -0
  29. package/dist/lib/utils/blockHelpers.d.ts.map +1 -0
  30. package/dist/lib/utils/blockHelpers.js +65 -0
  31. package/dist/lib/utils/slugify.d.ts +14 -0
  32. package/dist/lib/utils/slugify.d.ts.map +1 -0
  33. package/dist/lib/utils/slugify.js +37 -0
  34. package/dist/registry/BlockRegistry.d.ts +31 -0
  35. package/dist/registry/BlockRegistry.d.ts.map +1 -0
  36. package/dist/registry/BlockRegistry.js +34 -0
  37. package/dist/registry/index.d.ts +5 -0
  38. package/dist/registry/index.d.ts.map +1 -0
  39. package/dist/registry/index.js +4 -0
  40. package/dist/state/EditorContext.d.ts +44 -0
  41. package/dist/state/EditorContext.d.ts.map +1 -0
  42. package/dist/state/EditorContext.js +212 -0
  43. package/dist/state/index.d.ts +10 -0
  44. package/dist/state/index.d.ts.map +1 -0
  45. package/dist/state/index.js +6 -0
  46. package/dist/state/reducer.d.ts +11 -0
  47. package/dist/state/reducer.d.ts.map +1 -0
  48. package/dist/state/reducer.js +488 -0
  49. package/dist/state/types.d.ts +157 -0
  50. package/dist/state/types.d.ts.map +1 -0
  51. package/dist/state/types.js +26 -0
  52. package/dist/types/block.d.ts +230 -0
  53. package/dist/types/block.d.ts.map +1 -0
  54. package/dist/types/block.js +8 -0
  55. package/dist/types/newsletter.d.ts +129 -0
  56. package/dist/types/newsletter.d.ts.map +1 -0
  57. package/dist/types/newsletter.js +4 -0
  58. package/dist/types/registry.d.ts +13 -0
  59. package/dist/types/registry.d.ts.map +1 -0
  60. package/dist/types/registry.js +4 -0
  61. package/dist/views/CanvasEditor/BlockWrapper.d.ts +23 -0
  62. package/dist/views/CanvasEditor/BlockWrapper.d.ts.map +1 -0
  63. package/dist/views/CanvasEditor/BlockWrapper.js +44 -0
  64. package/dist/views/CanvasEditor/CanvasEditorView.d.ts +14 -0
  65. package/dist/views/CanvasEditor/CanvasEditorView.d.ts.map +1 -0
  66. package/dist/views/CanvasEditor/CanvasEditorView.js +139 -0
  67. package/dist/views/CanvasEditor/EditorBody.d.ts +24 -0
  68. package/dist/views/CanvasEditor/EditorBody.d.ts.map +1 -0
  69. package/dist/views/CanvasEditor/EditorBody.js +21 -0
  70. package/dist/views/CanvasEditor/EditorHeader.d.ts +12 -0
  71. package/dist/views/CanvasEditor/EditorHeader.d.ts.map +1 -0
  72. package/dist/views/CanvasEditor/EditorHeader.js +47 -0
  73. package/dist/views/CanvasEditor/components/CustomBlockItem.d.ts +10 -0
  74. package/dist/views/CanvasEditor/components/CustomBlockItem.d.ts.map +1 -0
  75. package/dist/views/CanvasEditor/components/CustomBlockItem.js +36 -0
  76. package/dist/views/CanvasEditor/components/EditorCanvas.d.ts +25 -0
  77. package/dist/views/CanvasEditor/components/EditorCanvas.d.ts.map +1 -0
  78. package/dist/views/CanvasEditor/components/EditorCanvas.js +397 -0
  79. package/dist/views/CanvasEditor/components/EditorLibrary.d.ts +7 -0
  80. package/dist/views/CanvasEditor/components/EditorLibrary.d.ts.map +1 -0
  81. package/dist/views/CanvasEditor/components/EditorLibrary.js +25 -0
  82. package/dist/views/CanvasEditor/components/EditorSidebar.d.ts +9 -0
  83. package/dist/views/CanvasEditor/components/EditorSidebar.d.ts.map +1 -0
  84. package/dist/views/CanvasEditor/components/EditorSidebar.js +16 -0
  85. package/dist/views/CanvasEditor/components/ErrorBanner.d.ts +6 -0
  86. package/dist/views/CanvasEditor/components/ErrorBanner.d.ts.map +1 -0
  87. package/dist/views/CanvasEditor/components/ErrorBanner.js +8 -0
  88. package/dist/views/CanvasEditor/components/LibraryItem.d.ts +10 -0
  89. package/dist/views/CanvasEditor/components/LibraryItem.d.ts.map +1 -0
  90. package/dist/views/CanvasEditor/components/LibraryItem.js +35 -0
  91. package/dist/views/CanvasEditor/components/SlashCommandDetector.d.ts +18 -0
  92. package/dist/views/CanvasEditor/components/SlashCommandDetector.d.ts.map +1 -0
  93. package/dist/views/CanvasEditor/components/SlashCommandDetector.js +164 -0
  94. package/dist/views/CanvasEditor/components/SlashCommandMenu.d.ts +22 -0
  95. package/dist/views/CanvasEditor/components/SlashCommandMenu.d.ts.map +1 -0
  96. package/dist/views/CanvasEditor/components/SlashCommandMenu.js +57 -0
  97. package/dist/views/CanvasEditor/components/index.d.ts +16 -0
  98. package/dist/views/CanvasEditor/components/index.d.ts.map +1 -0
  99. package/dist/views/CanvasEditor/components/index.js +9 -0
  100. package/dist/views/CanvasEditor/hooks/index.d.ts +7 -0
  101. package/dist/views/CanvasEditor/hooks/index.d.ts.map +1 -0
  102. package/dist/views/CanvasEditor/hooks/index.js +6 -0
  103. package/dist/views/CanvasEditor/hooks/useKeyboardShortcuts.d.ts +3 -0
  104. package/dist/views/CanvasEditor/hooks/useKeyboardShortcuts.d.ts.map +1 -0
  105. package/dist/views/CanvasEditor/hooks/useKeyboardShortcuts.js +114 -0
  106. package/dist/views/CanvasEditor/hooks/useNewsletterLoader.d.ts +5 -0
  107. package/dist/views/CanvasEditor/hooks/useNewsletterLoader.d.ts.map +1 -0
  108. package/dist/views/CanvasEditor/hooks/useNewsletterLoader.js +28 -0
  109. package/dist/views/CanvasEditor/hooks/useRegisteredBlocks.d.ts +2 -0
  110. package/dist/views/CanvasEditor/hooks/useRegisteredBlocks.d.ts.map +1 -0
  111. package/dist/views/CanvasEditor/hooks/useRegisteredBlocks.js +46 -0
  112. package/dist/views/CanvasEditor/hooks/useSlashCommand.d.ts +31 -0
  113. package/dist/views/CanvasEditor/hooks/useSlashCommand.d.ts.map +1 -0
  114. package/dist/views/CanvasEditor/hooks/useSlashCommand.js +87 -0
  115. package/dist/views/CanvasEditor/index.d.ts +12 -0
  116. package/dist/views/CanvasEditor/index.d.ts.map +1 -0
  117. package/dist/views/CanvasEditor/index.js +7 -0
  118. package/dist/views/NewsletterEditor.d.ts +16 -0
  119. package/dist/views/NewsletterEditor.d.ts.map +1 -0
  120. package/dist/views/NewsletterEditor.js +10 -0
  121. package/dist/views/NewsletterManager.d.ts +10 -0
  122. package/dist/views/NewsletterManager.d.ts.map +1 -0
  123. package/dist/views/NewsletterManager.js +95 -0
  124. package/dist/views/SettingsView.d.ts +10 -0
  125. package/dist/views/SettingsView.d.ts.map +1 -0
  126. package/dist/views/SettingsView.js +103 -0
  127. package/dist/views/SubscribersView.d.ts +10 -0
  128. package/dist/views/SubscribersView.d.ts.map +1 -0
  129. package/dist/views/SubscribersView.js +94 -0
  130. package/package.json +24 -23
  131. package/src/api/handler.ts +340 -1
  132. package/src/api/router.ts +35 -0
  133. package/src/index.tsx +284 -4
  134. package/src/index.tsx.patch +98 -0
  135. package/src/init.tsx +72 -0
  136. package/src/lib/blocks/BlockRenderer.tsx +125 -0
  137. package/src/lib/email/EmailRenderer.tsx +425 -0
  138. package/src/lib/email/index.ts +6 -0
  139. package/src/lib/mappers/apiMapper.ts +57 -0
  140. package/src/lib/utils/blockHelpers.ts +71 -0
  141. package/src/lib/utils/slugify.ts +43 -0
  142. package/src/registry/BlockRegistry.ts +53 -0
  143. package/src/registry/index.ts +5 -0
  144. package/src/state/EditorContext.tsx +279 -0
  145. package/src/state/index.ts +10 -0
  146. package/src/state/reducer.ts +561 -0
  147. package/src/state/types.ts +154 -0
  148. package/src/types/block.ts +275 -0
  149. package/src/types/newsletter.ts +114 -1
  150. package/src/types/registry.ts +14 -0
  151. package/src/views/CanvasEditor/BlockWrapper.tsx +143 -0
  152. package/src/views/CanvasEditor/CanvasEditorView.tsx +249 -0
  153. package/src/views/CanvasEditor/EditorBody.tsx +95 -0
  154. package/src/views/CanvasEditor/EditorHeader.tsx +139 -0
  155. package/src/views/CanvasEditor/components/CustomBlockItem.tsx +83 -0
  156. package/src/views/CanvasEditor/components/EditorCanvas.tsx +674 -0
  157. package/src/views/CanvasEditor/components/EditorLibrary.tsx +120 -0
  158. package/src/views/CanvasEditor/components/EditorSidebar.tsx +156 -0
  159. package/src/views/CanvasEditor/components/ErrorBanner.tsx +31 -0
  160. package/src/views/CanvasEditor/components/LibraryItem.tsx +71 -0
  161. package/src/views/CanvasEditor/components/SlashCommandDetector.tsx +196 -0
  162. package/src/views/CanvasEditor/components/SlashCommandMenu.tsx +131 -0
  163. package/src/views/CanvasEditor/components/index.ts +16 -0
  164. package/src/views/CanvasEditor/hooks/index.ts +7 -0
  165. package/src/views/CanvasEditor/hooks/useKeyboardShortcuts.ts +136 -0
  166. package/src/views/CanvasEditor/hooks/useNewsletterLoader.ts +34 -0
  167. package/src/views/CanvasEditor/hooks/useRegisteredBlocks.ts +54 -0
  168. package/src/views/CanvasEditor/hooks/useSlashCommand.ts +106 -0
  169. package/src/views/CanvasEditor/index.ts +12 -0
  170. package/src/views/NewsletterEditor.tsx +38 -0
  171. package/src/views/NewsletterManager.tsx +240 -0
  172. package/src/views/SettingsView.tsx +14 -14
  173. package/src/views/SubscribersView.tsx +20 -20
@@ -0,0 +1,28 @@
1
+ import { useEffect, useState } from 'react';
2
+ export function useNewsletterLoader(newsletterSlug, currentNewsletterId, loadNewsletter) {
3
+ const [isLoadingNewsletter, setIsLoadingNewsletter] = useState(false);
4
+ useEffect(() => {
5
+ if (newsletterSlug && !currentNewsletterId) {
6
+ const loadNewsletterData = async () => {
7
+ try {
8
+ setIsLoadingNewsletter(true);
9
+ const response = await fetch(`/api/plugin-newsletter/newsletters/${newsletterSlug}`);
10
+ if (!response.ok) {
11
+ throw new Error('Failed to load newsletter');
12
+ }
13
+ const newsletter = await response.json();
14
+ loadNewsletter(newsletter);
15
+ }
16
+ catch (error) {
17
+ console.error('Failed to load newsletter:', error);
18
+ alert('Failed to load newsletter. Please try again.');
19
+ }
20
+ finally {
21
+ setIsLoadingNewsletter(false);
22
+ }
23
+ };
24
+ loadNewsletterData();
25
+ }
26
+ }, [newsletterSlug, currentNewsletterId, loadNewsletter]);
27
+ return { isLoadingNewsletter };
28
+ }
@@ -0,0 +1,2 @@
1
+ export declare function useRegisteredBlocks(): import("../../..").BlockTypeDefinition[];
2
+ //# sourceMappingURL=useRegisteredBlocks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useRegisteredBlocks.d.ts","sourceRoot":"","sources":["../../../../src/views/CanvasEditor/hooks/useRegisteredBlocks.ts"],"names":[],"mappings":"AAGA,wBAAgB,mBAAmB,6CAkDlC"}
@@ -0,0 +1,46 @@
1
+ import { useState, useEffect } from 'react';
2
+ import { blockRegistry } from '../../../registry/BlockRegistry';
3
+ export function useRegisteredBlocks() {
4
+ const [registeredBlocks, setRegisteredBlocks] = useState(() => {
5
+ const initial = blockRegistry.getAll();
6
+ return initial;
7
+ });
8
+ // Watch for registry changes and update state
9
+ useEffect(() => {
10
+ // Check immediately
11
+ const checkBlocks = () => {
12
+ const currentBlocks = blockRegistry.getAll();
13
+ const hasChanged = currentBlocks.length !== registeredBlocks.length ||
14
+ currentBlocks.some((b, i) => b.type !== registeredBlocks[i]?.type) ||
15
+ registeredBlocks.some((b, i) => b.type !== currentBlocks[i]?.type);
16
+ if (hasChanged) {
17
+ setRegisteredBlocks([...currentBlocks]);
18
+ }
19
+ };
20
+ // Initial check
21
+ checkBlocks();
22
+ // Poll for registry changes (blocks are registered asynchronously in useEffect)
23
+ let pollCount = 0;
24
+ const interval = setInterval(() => {
25
+ pollCount++;
26
+ checkBlocks();
27
+ // Stop polling after 5 seconds (25 checks at 200ms)
28
+ if (pollCount > 25) {
29
+ clearInterval(interval);
30
+ }
31
+ }, 200);
32
+ // Also check after delays to catch initial registrations
33
+ const timeouts = [
34
+ setTimeout(checkBlocks, 50),
35
+ setTimeout(checkBlocks, 100),
36
+ setTimeout(checkBlocks, 300),
37
+ setTimeout(checkBlocks, 500),
38
+ setTimeout(checkBlocks, 1000),
39
+ ];
40
+ return () => {
41
+ clearInterval(interval);
42
+ timeouts.forEach(clearTimeout);
43
+ };
44
+ }, [registeredBlocks.length]);
45
+ return registeredBlocks;
46
+ }
@@ -0,0 +1,31 @@
1
+ import type { BlockTypeDefinition } from '../../../types/block';
2
+ export interface SlashCommandState {
3
+ isOpen: boolean;
4
+ query: string;
5
+ selectedIndex: number;
6
+ position: {
7
+ top: number;
8
+ left: number;
9
+ } | null;
10
+ replaceBlockId?: string;
11
+ }
12
+ export declare function useSlashCommand(blocks: BlockTypeDefinition[], onSelectBlock: (blockType: string, replaceBlockId?: string) => void): {
13
+ isOpen: boolean;
14
+ query: string;
15
+ selectedIndex: number;
16
+ position: {
17
+ top: number;
18
+ left: number;
19
+ } | null;
20
+ replaceBlockId: string | undefined;
21
+ filteredBlocks: BlockTypeDefinition[];
22
+ openMenu: (position: {
23
+ top: number;
24
+ left: number;
25
+ }, replaceBlockId?: string) => void;
26
+ closeMenu: () => void;
27
+ updateQuery: (query: string) => void;
28
+ moveSelection: (direction: "up" | "down") => void;
29
+ selectCurrent: (replaceBlockId?: string) => void;
30
+ };
31
+ //# sourceMappingURL=useSlashCommand.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useSlashCommand.d.ts","sourceRoot":"","sources":["../../../../src/views/CanvasEditor/hooks/useSlashCommand.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAEhE,MAAM,WAAW,iBAAiB;IAC9B,MAAM,EAAE,OAAO,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC/C,cAAc,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,wBAAgB,eAAe,CAC3B,MAAM,EAAE,mBAAmB,EAAE,EAC7B,aAAa,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,cAAc,CAAC,EAAE,MAAM,KAAK,IAAI;;;;;aANlD,MAAM;cAAQ,MAAM;;;;yBAyBG;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,mBAAmB,MAAM;;yBAoBtD,MAAM;+BAQA,IAAI,GAAG,MAAM;qCAkBP,MAAM;EA2B7D"}
@@ -0,0 +1,87 @@
1
+ import { useState, useCallback } from 'react';
2
+ export function useSlashCommand(blocks, onSelectBlock) {
3
+ const [state, setState] = useState({
4
+ isOpen: false,
5
+ query: '',
6
+ selectedIndex: 0,
7
+ position: null,
8
+ });
9
+ // Filter blocks based on query
10
+ const filteredBlocks = blocks.filter(block => {
11
+ if (!state.query)
12
+ return true;
13
+ const lowerQuery = state.query.toLowerCase();
14
+ const name = block.name.toLowerCase();
15
+ const description = block.description?.toLowerCase() || '';
16
+ const type = block.type.toLowerCase();
17
+ return name.includes(lowerQuery) || description.includes(lowerQuery) || type.includes(lowerQuery);
18
+ });
19
+ const openMenu = useCallback((position, replaceBlockId) => {
20
+ setState({
21
+ isOpen: true,
22
+ query: '',
23
+ selectedIndex: 0,
24
+ position,
25
+ replaceBlockId,
26
+ });
27
+ }, []);
28
+ const closeMenu = useCallback(() => {
29
+ setState({
30
+ isOpen: false,
31
+ query: '',
32
+ selectedIndex: 0,
33
+ position: null,
34
+ replaceBlockId: undefined,
35
+ });
36
+ }, []);
37
+ const updateQuery = useCallback((query) => {
38
+ setState(prev => ({
39
+ ...prev,
40
+ query,
41
+ selectedIndex: 0, // Reset selection when query changes
42
+ }));
43
+ }, []);
44
+ const moveSelection = useCallback((direction) => {
45
+ setState(prev => {
46
+ if (!prev.isOpen || filteredBlocks.length === 0)
47
+ return prev;
48
+ let newIndex = prev.selectedIndex;
49
+ if (direction === 'down') {
50
+ newIndex = (prev.selectedIndex + 1) % filteredBlocks.length;
51
+ }
52
+ else {
53
+ newIndex = prev.selectedIndex === 0 ? filteredBlocks.length - 1 : prev.selectedIndex - 1;
54
+ }
55
+ return {
56
+ ...prev,
57
+ selectedIndex: newIndex,
58
+ };
59
+ });
60
+ }, [filteredBlocks.length]);
61
+ const selectCurrent = useCallback((replaceBlockId) => {
62
+ if (state.isOpen && filteredBlocks[state.selectedIndex]) {
63
+ // Always use the replaceBlockId from state (set when menu was opened)
64
+ // The parameter is ignored to ensure we use the correct block ID
65
+ const blockIdToReplace = state.replaceBlockId;
66
+ if (!blockIdToReplace) {
67
+ console.warn('[useSlashCommand] No replaceBlockId in state when selecting block');
68
+ return;
69
+ }
70
+ onSelectBlock(filteredBlocks[state.selectedIndex].type, blockIdToReplace);
71
+ closeMenu();
72
+ }
73
+ }, [state.isOpen, state.selectedIndex, state.replaceBlockId, filteredBlocks, onSelectBlock, closeMenu]);
74
+ return {
75
+ isOpen: state.isOpen,
76
+ query: state.query,
77
+ selectedIndex: state.selectedIndex,
78
+ position: state.position,
79
+ replaceBlockId: state.replaceBlockId,
80
+ filteredBlocks,
81
+ openMenu,
82
+ closeMenu,
83
+ updateQuery,
84
+ moveSelection,
85
+ selectCurrent,
86
+ };
87
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Newsletter Canvas Editor Exports
3
+ */
4
+ export { CanvasEditorView } from './CanvasEditorView';
5
+ export type { CanvasEditorViewProps } from './CanvasEditorView';
6
+ export { EditorBody } from './EditorBody';
7
+ export type { EditorBodyProps } from './EditorBody';
8
+ export { BlockWrapper } from './BlockWrapper';
9
+ export type { BlockWrapperProps } from './BlockWrapper';
10
+ export { EditorHeader } from './EditorHeader';
11
+ export type { EditorHeaderProps } from './EditorHeader';
12
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/views/CanvasEditor/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,YAAY,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAChE,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,YAAY,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,YAAY,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,YAAY,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Newsletter Canvas Editor Exports
3
+ */
4
+ export { CanvasEditorView } from './CanvasEditorView';
5
+ export { EditorBody } from './EditorBody';
6
+ export { BlockWrapper } from './BlockWrapper';
7
+ export { EditorHeader } from './EditorHeader';
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Newsletter Editor View
3
+ * Block-based editor for creating and editing newsletters
4
+ */
5
+ export interface NewsletterEditorViewProps {
6
+ newsletterSlug?: string;
7
+ siteId: string;
8
+ locale: string;
9
+ darkMode?: boolean;
10
+ backgroundColors?: {
11
+ light: string;
12
+ dark?: string;
13
+ };
14
+ }
15
+ export declare function NewsletterEditorView({ newsletterSlug, siteId, locale, darkMode, backgroundColors }: NewsletterEditorViewProps): import("react/jsx-runtime").JSX.Element;
16
+ //# sourceMappingURL=NewsletterEditor.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Newsletter Editor View
3
+ * Block-based editor for creating and editing newsletters
4
+ */
5
+ 'use client';
6
+ import { jsx as _jsx } from "react/jsx-runtime";
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 }));
10
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Newsletter Manager View
3
+ * List and manage newsletters
4
+ */
5
+ export interface NewsletterManagerViewProps {
6
+ siteId: string;
7
+ locale: string;
8
+ }
9
+ export declare function NewsletterManagerView({ siteId, locale }: NewsletterManagerViewProps): import("react/jsx-runtime").JSX.Element;
10
+ //# sourceMappingURL=NewsletterManager.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Newsletter Manager View
3
+ * List and manage newsletters
4
+ */
5
+ 'use client';
6
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
7
+ import { useState, useEffect } from 'react';
8
+ import { Plus, Mail, Calendar, Trash2, Edit2 } from 'lucide-react';
9
+ function getStatusBadgeColor(status) {
10
+ switch (status) {
11
+ case 'sent':
12
+ return 'bg-green-500/10 text-green-600 dark:text-green-400 border-green-500/20';
13
+ case 'scheduled':
14
+ return 'bg-blue-500/10 text-blue-600 dark:text-blue-400 border-blue-500/20';
15
+ case 'draft':
16
+ return 'bg-neutral-500/10 text-dashboard-text-secondary border-neutral-500/20';
17
+ case 'archived':
18
+ return 'bg-gray-500/10 text-gray-600 dark:text-gray-400 border-gray-500/20';
19
+ default:
20
+ return 'bg-neutral-500/10 text-dashboard-text-secondary border-neutral-500/20';
21
+ }
22
+ }
23
+ export function NewsletterManagerView({ siteId, locale }) {
24
+ const [newsletters, setNewsletters] = useState([]);
25
+ const [isLoading, setIsLoading] = useState(true);
26
+ const [statusFilter, setStatusFilter] = useState('all');
27
+ // Fetch newsletters
28
+ useEffect(() => {
29
+ const fetchNewsletters = async () => {
30
+ try {
31
+ setIsLoading(true);
32
+ const response = await fetch('/api/plugin-newsletter/newsletters', {
33
+ credentials: 'include',
34
+ });
35
+ if (!response.ok) {
36
+ throw new Error('Failed to fetch newsletters');
37
+ }
38
+ const data = await response.json();
39
+ setNewsletters(Array.isArray(data) ? data : []);
40
+ }
41
+ catch (error) {
42
+ console.error('Failed to load newsletters:', error);
43
+ }
44
+ finally {
45
+ setIsLoading(false);
46
+ }
47
+ };
48
+ fetchNewsletters();
49
+ }, []);
50
+ // Filter newsletters
51
+ const filteredNewsletters = statusFilter === 'all'
52
+ ? newsletters
53
+ : newsletters.filter(n => n.status === statusFilter);
54
+ // Handle create new newsletter
55
+ const handleCreate = () => {
56
+ window.location.href = '/dashboard/newsletter/new';
57
+ };
58
+ // Handle edit newsletter
59
+ const handleEdit = (slug) => {
60
+ window.location.href = `/dashboard/newsletter/editor/${slug}`;
61
+ };
62
+ // Handle delete newsletter
63
+ const handleDelete = async (slug, title) => {
64
+ if (!confirm(`Are you sure you want to delete "${title}"?`)) {
65
+ return;
66
+ }
67
+ try {
68
+ const response = await fetch(`/api/plugin-newsletter/newsletters/${slug}`, {
69
+ method: 'DELETE',
70
+ credentials: 'include',
71
+ });
72
+ if (!response.ok) {
73
+ throw new Error('Failed to delete newsletter');
74
+ }
75
+ // Remove from local state
76
+ setNewsletters(prev => prev.filter(n => n.slug !== slug));
77
+ }
78
+ catch (error) {
79
+ console.error('Failed to delete newsletter:', error);
80
+ alert(error.message || 'Failed to delete newsletter');
81
+ }
82
+ };
83
+ // Format date
84
+ const formatDate = (dateString) => {
85
+ if (!dateString)
86
+ return 'N/A';
87
+ const date = new Date(dateString);
88
+ return date.toLocaleDateString(locale, {
89
+ day: 'numeric',
90
+ month: 'short',
91
+ year: 'numeric',
92
+ });
93
+ };
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))) })] }) })) })] }) }));
95
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Newsletter Settings View
3
+ * Interface for managing newsletter welcome email settings
4
+ */
5
+ export interface SettingsViewProps {
6
+ siteId: string;
7
+ locale: string;
8
+ }
9
+ export declare function SettingsView({ siteId, locale }: SettingsViewProps): import("react/jsx-runtime").JSX.Element;
10
+ //# sourceMappingURL=SettingsView.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SettingsView.d.ts","sourceRoot":"","sources":["../../src/views/SettingsView.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAQH,MAAM,WAAW,iBAAiB;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAClB;AAID,wBAAgB,YAAY,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,iBAAiB,2CAoMjE"}
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Newsletter Settings View
3
+ * Interface for managing newsletter welcome email settings
4
+ */
5
+ 'use client';
6
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
7
+ import { useState, useEffect } from 'react';
8
+ import { Save, RefreshCw, Mail, Settings2, Globe } from 'lucide-react';
9
+ const SUPPORTED_LANGUAGES = ['nl', 'en', 'sv'];
10
+ export function SettingsView({ siteId, locale }) {
11
+ const [isLoading, setIsLoading] = useState(true);
12
+ const [isSaving, setIsSaving] = useState(false);
13
+ const [showSuccess, setShowSuccess] = useState(false);
14
+ const [settings, setSettings] = useState({
15
+ id: 'welcome_automation',
16
+ languages: {
17
+ nl: { title: '', message: '' },
18
+ en: { title: '', message: '' },
19
+ sv: { title: '', message: '' },
20
+ },
21
+ });
22
+ // Fetch settings on load
23
+ useEffect(() => {
24
+ const fetchSettings = async () => {
25
+ try {
26
+ setIsLoading(true);
27
+ const response = await fetch('/api/plugin-newsletter/settings', {
28
+ credentials: 'include',
29
+ });
30
+ if (!response.ok) {
31
+ throw new Error('Failed to fetch settings');
32
+ }
33
+ const data = await response.json();
34
+ if (data.languages) {
35
+ setSettings({
36
+ id: data.id || 'welcome_automation',
37
+ languages: {
38
+ nl: data.languages.nl || { title: '', message: '' },
39
+ en: data.languages.en || { title: '', message: '' },
40
+ sv: data.languages.sv || { title: '', message: '' },
41
+ },
42
+ });
43
+ }
44
+ }
45
+ catch (error) {
46
+ console.error('Failed to load settings:', error);
47
+ }
48
+ finally {
49
+ setIsLoading(false);
50
+ }
51
+ };
52
+ fetchSettings();
53
+ }, []);
54
+ // Save settings
55
+ const handleSave = async () => {
56
+ try {
57
+ setIsSaving(true);
58
+ const response = await fetch('/api/plugin-newsletter/settings', {
59
+ method: 'POST',
60
+ headers: { 'Content-Type': 'application/json' },
61
+ credentials: 'include',
62
+ body: JSON.stringify({
63
+ id: 'welcome_automation',
64
+ languages: settings.languages,
65
+ }),
66
+ });
67
+ if (!response.ok) {
68
+ const error = await response.json();
69
+ throw new Error(error.error || 'Failed to save settings');
70
+ }
71
+ setShowSuccess(true);
72
+ setTimeout(() => setShowSuccess(false), 3000);
73
+ }
74
+ catch (error) {
75
+ console.error('Failed to save settings:', error);
76
+ alert(error.message || 'Failed to save settings');
77
+ }
78
+ finally {
79
+ setIsSaving(false);
80
+ }
81
+ };
82
+ // Update language setting
83
+ const updateLanguageSetting = (lang, field, value) => {
84
+ setSettings(prev => ({
85
+ ...prev,
86
+ languages: {
87
+ ...prev.languages,
88
+ [lang]: {
89
+ ...prev.languages[lang],
90
+ [field]: value,
91
+ },
92
+ },
93
+ }));
94
+ };
95
+ if (isLoading) {
96
+ return (_jsx("div", { className: "h-full w-full bg-white dark:bg-neutral-900 text-dashboard-text flex items-center justify-center", children: _jsxs("div", { className: "text-center", children: [_jsx(RefreshCw, { className: "w-8 h-8 animate-spin text-primary mx-auto mb-4" }), _jsx("p", { className: "text-sm text-dashboard-text-secondary", children: "Loading settings..." })] }) }));
97
+ }
98
+ 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-6xl 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: "Newsletter Settings" }), _jsx("p", { className: "text-sm text-dashboard-text-secondary", children: "Configure welcome emails for new subscribers" })] }), _jsx("button", { onClick: handleSave, disabled: isSaving, 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 ${isSaving
99
+ ? 'bg-neutral-400 text-white cursor-not-allowed'
100
+ : showSuccess
101
+ ? 'bg-green-600 text-white'
102
+ : 'bg-primary text-white hover:bg-primary/90'}`, children: isSaving ? (_jsxs(_Fragment, { children: [_jsx(RefreshCw, { className: "w-4 h-4 animate-spin" }), "Saving..."] })) : showSuccess ? (_jsxs(_Fragment, { children: [_jsx(Settings2, { className: "w-4 h-4" }), "Saved!"] })) : (_jsxs(_Fragment, { children: [_jsx(Save, { className: "w-4 h-4" }), "Save Settings"] })) })] }), _jsxs("section", { className: "bg-dashboard-sidebar p-8 rounded-3xl border border-dashboard-border mb-8", children: [_jsxs("div", { className: "flex items-center gap-2 font-bold text-dashboard-text border-b border-dashboard-border pb-4 mb-6", children: [_jsx(Mail, { size: 20, className: "text-primary" }), "Welcome Email Automation"] }), _jsx("p", { className: "text-sm text-dashboard-text-secondary mb-8", children: "Configure the welcome email that will be sent automatically when someone subscribes to your newsletter." }), _jsx("div", { className: "space-y-8", children: SUPPORTED_LANGUAGES.map(lang => (_jsxs("div", { className: "bg-dashboard-card p-6 rounded-2xl border border-dashboard-border", children: [_jsxs("div", { className: "flex items-center gap-2 mb-6", children: [_jsx(Globe, { size: 16, className: "text-primary" }), _jsxs("h3", { className: "text-lg font-black text-dashboard-text uppercase tracking-tight", children: [lang.toUpperCase(), " - ", lang === 'nl' ? 'Dutch' : lang === 'en' ? 'English' : 'Swedish'] })] }), _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 Subject" }), _jsx("input", { type: "text", value: settings.languages[lang]?.title || '', onChange: (e) => updateLanguageSetting(lang, 'title', e.target.value), placeholder: `Welcome email subject (${lang.toUpperCase()})`, 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: "Email Message" }), _jsx("textarea", { value: settings.languages[lang]?.message || '', onChange: (e) => updateLanguageSetting(lang, 'message', e.target.value), placeholder: `Welcome email message (${lang.toUpperCase()}). Use **bold** for bold text and • for bullet points.`, rows: 8, 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 resize-none font-mono text-sm" }), _jsx("p", { className: "text-[10px] text-dashboard-text-secondary mt-2", children: "Supports markdown: **bold**, \u2022 bullet points, line breaks" })] })] })] }, lang))) })] })] }) }));
103
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Newsletter Subscribers View
3
+ * Main interface for managing newsletter subscribers
4
+ */
5
+ export interface SubscribersViewProps {
6
+ siteId: string;
7
+ locale: string;
8
+ }
9
+ export declare function SubscribersView({ siteId, locale }: SubscribersViewProps): import("react/jsx-runtime").JSX.Element;
10
+ //# sourceMappingURL=SubscribersView.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SubscribersView.d.ts","sourceRoot":"","sources":["../../src/views/SubscribersView.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAQH,MAAM,WAAW,oBAAoB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAClB;AAED,wBAAgB,eAAe,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,oBAAoB,2CA2PvE"}
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Newsletter Subscribers View
3
+ * Main interface for managing newsletter subscribers
4
+ */
5
+ 'use client';
6
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
7
+ import { useState, useEffect } from 'react';
8
+ import { Mail, Copy, Check, Trash2, Filter, Users, Globe, Calendar } from 'lucide-react';
9
+ export function SubscribersView({ siteId, locale }) {
10
+ const [subscribers, setSubscribers] = useState([]);
11
+ const [isLoading, setIsLoading] = useState(true);
12
+ const [filter, setFilter] = useState('all');
13
+ const [copyStatus, setCopyStatus] = useState(false);
14
+ const [deleteStatus, setDeleteStatus] = useState(null);
15
+ // Fetch subscribers on load
16
+ useEffect(() => {
17
+ const fetchSubscribers = async () => {
18
+ try {
19
+ setIsLoading(true);
20
+ const response = await fetch('/api/plugin-newsletter/subscribers', {
21
+ credentials: 'include',
22
+ });
23
+ if (!response.ok) {
24
+ throw new Error('Failed to fetch subscribers');
25
+ }
26
+ const data = await response.json();
27
+ setSubscribers(Array.isArray(data) ? data : []);
28
+ }
29
+ catch (error) {
30
+ console.error('Failed to load subscribers:', error);
31
+ }
32
+ finally {
33
+ setIsLoading(false);
34
+ }
35
+ };
36
+ fetchSubscribers();
37
+ }, []);
38
+ // Filter subscribers
39
+ const filteredSubscribers = filter === 'all'
40
+ ? subscribers
41
+ : subscribers.filter(s => s.language === filter);
42
+ // Get unique languages
43
+ const languages = Array.from(new Set(subscribers.map(s => s.language))).sort();
44
+ // Copy emails to clipboard
45
+ const handleCopyEmails = async () => {
46
+ const emails = filteredSubscribers.map(s => s.email).join(', ');
47
+ try {
48
+ await navigator.clipboard.writeText(emails);
49
+ setCopyStatus(true);
50
+ setTimeout(() => setCopyStatus(false), 2000);
51
+ }
52
+ catch (error) {
53
+ console.error('Failed to copy emails:', error);
54
+ }
55
+ };
56
+ // Delete subscriber
57
+ const handleDeleteSubscriber = async (email) => {
58
+ if (!confirm(`Are you sure you want to remove ${email} from the newsletter?`)) {
59
+ return;
60
+ }
61
+ try {
62
+ setDeleteStatus(email);
63
+ const response = await fetch(`/api/plugin-newsletter/subscribers/${encodeURIComponent(email)}`, {
64
+ method: 'DELETE',
65
+ credentials: 'include',
66
+ });
67
+ if (!response.ok) {
68
+ const error = await response.json();
69
+ throw new Error(error.error || 'Failed to delete subscriber');
70
+ }
71
+ // Remove from local state
72
+ setSubscribers(prev => prev.filter(sub => sub.email !== email));
73
+ }
74
+ catch (error) {
75
+ console.error('Failed to delete subscriber:', error);
76
+ alert(error.message || 'Failed to delete subscriber');
77
+ }
78
+ finally {
79
+ setDeleteStatus(null);
80
+ }
81
+ };
82
+ // Format date
83
+ const formatDate = (dateString) => {
84
+ if (!dateString)
85
+ return 'N/A';
86
+ const date = typeof dateString === 'string' ? new Date(dateString) : dateString;
87
+ return date.toLocaleDateString(locale, {
88
+ day: 'numeric',
89
+ month: 'short',
90
+ year: 'numeric',
91
+ });
92
+ };
93
+ 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: "Newsletter Subscribers" }), _jsx("p", { className: "text-sm text-dashboard-text-secondary", children: "Manage your newsletter subscribers and their preferences" })] }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsxs("div", { className: "flex items-center bg-dashboard-bg border border-dashboard-border rounded-xl px-4 py-2.5", children: [_jsx(Filter, { size: 16, className: "text-primary mr-2" }), _jsxs("select", { value: filter, onChange: (e) => setFilter(e.target.value), className: "bg-transparent text-xs font-bold text-dashboard-text outline-none cursor-pointer uppercase tracking-widest", children: [_jsx("option", { value: "all", children: "All Languages" }), languages.map(lang => (_jsx("option", { value: lang, children: lang.toUpperCase() }, lang)))] })] }), _jsx("button", { onClick: handleCopyEmails, disabled: filteredSubscribers.length === 0, 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 disabled:opacity-30 disabled:cursor-not-allowed bg-primary text-white hover:bg-primary/90", children: copyStatus ? (_jsxs(_Fragment, { children: [_jsx(Check, { size: 14 }), "Copied!"] })) : (_jsxs(_Fragment, { children: [_jsx(Copy, { size: 14 }), "Copy List"] })) })] })] }), _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-3 gap-6 mb-8", children: [_jsxs("div", { className: "bg-dashboard-bg p-6 rounded-3xl border border-dashboard-border", children: [_jsxs("div", { className: "flex items-center gap-3 mb-2", children: [_jsx(Users, { size: 20, className: "text-primary" }), _jsx("span", { className: "text-[10px] uppercase tracking-widest text-dashboard-text-secondary font-black", children: "Total Subscribers" })] }), _jsx("p", { className: "text-3xl font-black text-dashboard-text", children: subscribers.length })] }), _jsxs("div", { className: "bg-dashboard-bg p-6 rounded-3xl border border-dashboard-border", children: [_jsxs("div", { className: "flex items-center gap-3 mb-2", children: [_jsx(Globe, { size: 20, className: "text-primary" }), _jsx("span", { className: "text-[10px] uppercase tracking-widest text-dashboard-text-secondary font-black", children: "Languages" })] }), _jsx("p", { className: "text-3xl font-black text-dashboard-text", children: languages.length })] }), _jsxs("div", { className: "bg-dashboard-bg p-6 rounded-3xl border border-dashboard-border", children: [_jsxs("div", { className: "flex items-center gap-3 mb-2", children: [_jsx(Filter, { size: 20, className: "text-primary" }), _jsx("span", { className: "text-[10px] uppercase tracking-widest text-dashboard-text-secondary font-black", children: "Filtered" })] }), _jsx("p", { className: "text-3xl font-black text-dashboard-text", children: filteredSubscribers.length })] })] }), _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" }) })) : filteredSubscribers.length === 0 ? (_jsxs("div", { className: "py-24 text-center", children: [_jsx(Users, { size: 64, className: "mx-auto text-dashboard-text-secondary mb-4" }), _jsx("p", { className: "text-dashboard-text-secondary font-serif italic text-lg", children: filter === 'all' ? 'No subscribers yet.' : `No subscribers found for ${filter.toUpperCase()}.` })] })) : (_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: "Subscriber" }), _jsx("th", { className: "px-8 py-5", children: "Language" }), _jsx("th", { className: "px-8 py-5 text-right", children: "Subscribed" }), _jsx("th", { className: "px-8 py-5 text-right", children: "Actions" })] }) }), _jsx("tbody", { className: "divide-y divide-dashboard-border", children: filteredSubscribers.map((subscriber, idx) => (_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: subscriber.email })] }) }), _jsx("td", { className: "px-8 py-5", children: _jsx("span", { className: "text-[10px] font-black px-3 py-1 bg-primary/10 rounded-full uppercase text-primary border border-primary/20", children: subscriber.language }) }), _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(subscriber.subscribedAt)] }) }), _jsx("td", { className: "px-8 py-5 text-right", children: _jsx("button", { onClick: () => handleDeleteSubscriber(subscriber.email), disabled: deleteStatus === subscriber.email, 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 disabled:opacity-50 disabled:cursor-not-allowed", title: "Remove subscriber", children: deleteStatus === subscriber.email ? (_jsx("div", { className: "w-5 h-5 border-2 border-red-500 border-t-transparent rounded-full animate-spin" })) : (_jsx(Trash2, { size: 18 })) }) })] }, idx))) })] }) })) })] }) }));
94
+ }