@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,240 @@
1
+ /**
2
+ * Newsletter Manager View
3
+ * List and manage newsletters
4
+ */
5
+
6
+ 'use client';
7
+
8
+ import React, { useState, useEffect } from 'react';
9
+ import { Plus, Mail, Calendar, Trash2, Edit2 } from 'lucide-react';
10
+ import { NewsletterListItem, NewsletterStatus } from '../types/newsletter';
11
+
12
+ export interface NewsletterManagerViewProps {
13
+ siteId: string;
14
+ locale: string;
15
+ }
16
+
17
+ function getStatusBadgeColor(status: NewsletterStatus) {
18
+ switch (status) {
19
+ case 'sent':
20
+ return 'bg-green-500/10 text-green-600 dark:text-green-400 border-green-500/20';
21
+ case 'scheduled':
22
+ return 'bg-blue-500/10 text-blue-600 dark:text-blue-400 border-blue-500/20';
23
+ case 'draft':
24
+ return 'bg-neutral-500/10 text-dashboard-text-secondary border-neutral-500/20';
25
+ case 'archived':
26
+ return 'bg-gray-500/10 text-gray-600 dark:text-gray-400 border-gray-500/20';
27
+ default:
28
+ return 'bg-neutral-500/10 text-dashboard-text-secondary border-neutral-500/20';
29
+ }
30
+ }
31
+
32
+ export function NewsletterManagerView({ siteId, locale }: NewsletterManagerViewProps) {
33
+ const [newsletters, setNewsletters] = useState<NewsletterListItem[]>([]);
34
+ const [isLoading, setIsLoading] = useState(true);
35
+ const [statusFilter, setStatusFilter] = useState<NewsletterStatus | 'all'>('all');
36
+
37
+ // Fetch newsletters
38
+ useEffect(() => {
39
+ const fetchNewsletters = async () => {
40
+ try {
41
+ setIsLoading(true);
42
+ const response = await fetch('/api/plugin-newsletter/newsletters', {
43
+ credentials: 'include',
44
+ });
45
+ if (!response.ok) {
46
+ throw new Error('Failed to fetch newsletters');
47
+ }
48
+ const data = await response.json();
49
+ setNewsletters(Array.isArray(data) ? data : []);
50
+ } catch (error) {
51
+ console.error('Failed to load newsletters:', error);
52
+ } finally {
53
+ setIsLoading(false);
54
+ }
55
+ };
56
+ fetchNewsletters();
57
+ }, []);
58
+
59
+ // Filter newsletters
60
+ const filteredNewsletters = statusFilter === 'all'
61
+ ? newsletters
62
+ : newsletters.filter(n => n.status === statusFilter);
63
+
64
+ // Handle create new newsletter
65
+ const handleCreate = () => {
66
+ window.location.href = '/dashboard/newsletter/new';
67
+ };
68
+
69
+ // Handle edit newsletter
70
+ const handleEdit = (slug: string) => {
71
+ window.location.href = `/dashboard/newsletter/editor/${slug}`;
72
+ };
73
+
74
+ // Handle delete newsletter
75
+ const handleDelete = async (slug: string, title: string) => {
76
+ if (!confirm(`Are you sure you want to delete "${title}"?`)) {
77
+ return;
78
+ }
79
+
80
+ try {
81
+ const response = await fetch(`/api/plugin-newsletter/newsletters/${slug}`, {
82
+ method: 'DELETE',
83
+ credentials: 'include',
84
+ });
85
+
86
+ if (!response.ok) {
87
+ throw new Error('Failed to delete newsletter');
88
+ }
89
+
90
+ // Remove from local state
91
+ setNewsletters(prev => prev.filter(n => n.slug !== slug));
92
+ } catch (error: any) {
93
+ console.error('Failed to delete newsletter:', error);
94
+ alert(error.message || 'Failed to delete newsletter');
95
+ }
96
+ };
97
+
98
+ // Format date
99
+ const formatDate = (dateString: string | undefined) => {
100
+ if (!dateString) return 'N/A';
101
+ const date = new Date(dateString);
102
+ return date.toLocaleDateString(locale, {
103
+ day: 'numeric',
104
+ month: 'short',
105
+ year: 'numeric',
106
+ });
107
+ };
108
+
109
+ return (
110
+ <div className="h-full w-full rounded-[2.5rem] bg-white dark:bg-neutral-900 p-8 overflow-y-auto">
111
+ <div className="max-w-7xl mx-auto">
112
+ {/* Header */}
113
+ <div className="flex flex-col md:flex-row md:items-center justify-between gap-6 mb-8">
114
+ <div>
115
+ <h1 className="text-3xl font-black text-dashboard-text uppercase tracking-tighter mb-2">
116
+ Newsletters
117
+ </h1>
118
+ <p className="text-sm text-dashboard-text-secondary">
119
+ Create and manage your email newsletters
120
+ </p>
121
+ </div>
122
+
123
+ <div className="flex items-center gap-3">
124
+ {/* Status Filter */}
125
+ <select
126
+ value={statusFilter}
127
+ onChange={(e) => setStatusFilter(e.target.value as NewsletterStatus | 'all')}
128
+ 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"
129
+ >
130
+ <option value="all">All Status</option>
131
+ <option value="draft">Draft</option>
132
+ <option value="scheduled">Scheduled</option>
133
+ <option value="sent">Sent</option>
134
+ <option value="archived">Archived</option>
135
+ </select>
136
+
137
+ {/* Create Button */}
138
+ <button
139
+ onClick={handleCreate}
140
+ 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"
141
+ >
142
+ <Plus size={14} />
143
+ New Newsletter
144
+ </button>
145
+ </div>
146
+ </div>
147
+
148
+ {/* Newsletters Table */}
149
+ <div className="bg-dashboard-bg rounded-3xl border border-dashboard-border overflow-hidden">
150
+ {isLoading ? (
151
+ <div className="flex items-center justify-center py-20">
152
+ <div className="w-8 h-8 border-4 border-primary/20 border-t-primary rounded-full animate-spin" />
153
+ </div>
154
+ ) : filteredNewsletters.length === 0 ? (
155
+ <div className="py-24 text-center">
156
+ <Mail size={64} className="mx-auto text-dashboard-text-secondary mb-4" />
157
+ <p className="text-dashboard-text-secondary font-serif italic text-lg mb-6">
158
+ {statusFilter === 'all' ? 'No newsletters yet.' : `No newsletters found with status "${statusFilter}".`}
159
+ </p>
160
+ <button
161
+ onClick={handleCreate}
162
+ 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"
163
+ >
164
+ <Plus size={14} />
165
+ Create Your First Newsletter
166
+ </button>
167
+ </div>
168
+ ) : (
169
+ <div className="overflow-x-auto">
170
+ <table className="w-full text-left border-collapse">
171
+ <thead>
172
+ <tr className="bg-dashboard-bg text-dashboard-text text-[10px] uppercase tracking-[0.2em] font-black border-b border-dashboard-border">
173
+ <th className="px-8 py-5">Title</th>
174
+ <th className="px-8 py-5">Subject</th>
175
+ <th className="px-8 py-5">Status</th>
176
+ <th className="px-8 py-5 text-right">Updated</th>
177
+ <th className="px-8 py-5 text-right">Actions</th>
178
+ </tr>
179
+ </thead>
180
+ <tbody className="divide-y divide-dashboard-border">
181
+ {filteredNewsletters.map((newsletter) => (
182
+ <tr
183
+ key={newsletter.id}
184
+ className="hover:bg-dashboard-bg transition-colors group"
185
+ >
186
+ <td className="px-8 py-5">
187
+ <div className="flex items-center gap-4">
188
+ <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">
189
+ <Mail size={18} />
190
+ </div>
191
+ <span className="text-sm font-medium text-dashboard-text tracking-tight">
192
+ {newsletter.title}
193
+ </span>
194
+ </div>
195
+ </td>
196
+ <td className="px-8 py-5">
197
+ <span className="text-sm text-dashboard-text-secondary">
198
+ {newsletter.subject || 'No subject'}
199
+ </span>
200
+ </td>
201
+ <td className="px-8 py-5">
202
+ <span className={`text-[10px] font-black px-3 py-1 rounded-full uppercase border ${getStatusBadgeColor(newsletter.status)}`}>
203
+ {newsletter.status}
204
+ </span>
205
+ </td>
206
+ <td className="px-8 py-5 text-right text-xs text-dashboard-text-secondary font-medium">
207
+ <div className="flex items-center justify-end gap-2">
208
+ <Calendar size={14} />
209
+ {formatDate(newsletter.updatedAt)}
210
+ </div>
211
+ </td>
212
+ <td className="px-8 py-5 text-right">
213
+ <div className="flex items-center justify-end gap-2">
214
+ <button
215
+ onClick={() => handleEdit(newsletter.slug)}
216
+ className="p-2.5 rounded-full text-dashboard-text-secondary hover:text-primary hover:bg-primary/10 transition-colors"
217
+ title="Edit newsletter"
218
+ >
219
+ <Edit2 size={18} />
220
+ </button>
221
+ <button
222
+ onClick={() => handleDelete(newsletter.slug, newsletter.title)}
223
+ 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"
224
+ title="Delete newsletter"
225
+ >
226
+ <Trash2 size={18} />
227
+ </button>
228
+ </div>
229
+ </td>
230
+ </tr>
231
+ ))}
232
+ </tbody>
233
+ </table>
234
+ </div>
235
+ )}
236
+ </div>
237
+ </div>
238
+ </div>
239
+ );
240
+ }
@@ -105,32 +105,32 @@ export function SettingsView({ siteId, locale }: SettingsViewProps) {
105
105
 
106
106
  if (isLoading) {
107
107
  return (
108
- <div className="h-full w-full bg-dashboard-card text-dashboard-text flex items-center justify-center">
108
+ <div className="h-full w-full bg-white dark:bg-neutral-900 text-dashboard-text flex items-center justify-center">
109
109
  <div className="text-center">
110
110
  <RefreshCw className="w-8 h-8 animate-spin text-primary mx-auto mb-4" />
111
- <p className="text-sm text-neutral-500 dark:text-neutral-400">Loading settings...</p>
111
+ <p className="text-sm text-dashboard-text-secondary">Loading settings...</p>
112
112
  </div>
113
113
  </div>
114
114
  );
115
115
  }
116
116
 
117
117
  return (
118
- <div className="h-full w-full rounded-[2.5rem] bg-dashboard-card text-dashboard-text overflow-y-auto">
119
- <div className="max-w-6xl mx-auto p-8">
118
+ <div className="h-full w-full rounded-[2.5rem] bg-white dark:bg-neutral-900 p-8 overflow-y-auto">
119
+ <div className="max-w-6xl mx-auto">
120
120
  {/* Header */}
121
- <div className="flex items-center justify-between mb-8">
121
+ <div className="flex flex-col md:flex-row md:items-center justify-between gap-6 mb-8">
122
122
  <div>
123
- <h1 className="text-3xl font-black text-neutral-950 dark:text-white uppercase tracking-tighter mb-2">
123
+ <h1 className="text-3xl font-black text-dashboard-text uppercase tracking-tighter mb-2">
124
124
  Newsletter Settings
125
125
  </h1>
126
- <p className="text-sm text-neutral-500 dark:text-neutral-400">
126
+ <p className="text-sm text-dashboard-text-secondary">
127
127
  Configure welcome emails for new subscribers
128
128
  </p>
129
129
  </div>
130
130
  <button
131
131
  onClick={handleSave}
132
132
  disabled={isSaving}
133
- className={`inline-flex items-center gap-2 px-6 py-3 rounded-full text-[10px] font-black uppercase tracking-widest transition-colors shadow-lg ${isSaving
133
+ 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
134
134
  ? 'bg-neutral-400 text-white cursor-not-allowed'
135
135
  : showSuccess
136
136
  ? 'bg-green-600 text-white'
@@ -158,11 +158,11 @@ export function SettingsView({ siteId, locale }: SettingsViewProps) {
158
158
 
159
159
  {/* Welcome Email Settings */}
160
160
  <section className="bg-dashboard-sidebar p-8 rounded-3xl border border-dashboard-border mb-8">
161
- <div className="flex items-center gap-2 font-bold text-neutral-950 dark:text-white border-b border-dashboard-border pb-4 mb-6">
161
+ <div className="flex items-center gap-2 font-bold text-dashboard-text border-b border-dashboard-border pb-4 mb-6">
162
162
  <Mail size={20} className="text-primary" />
163
163
  Welcome Email Automation
164
164
  </div>
165
- <p className="text-sm text-neutral-500 dark:text-neutral-400 mb-8">
165
+ <p className="text-sm text-dashboard-text-secondary mb-8">
166
166
  Configure the welcome email that will be sent automatically when someone subscribes to your newsletter.
167
167
  </p>
168
168
 
@@ -172,13 +172,13 @@ export function SettingsView({ siteId, locale }: SettingsViewProps) {
172
172
  <div key={lang} className="bg-dashboard-card p-6 rounded-2xl border border-dashboard-border">
173
173
  <div className="flex items-center gap-2 mb-6">
174
174
  <Globe size={16} className="text-primary" />
175
- <h3 className="text-lg font-black text-neutral-950 dark:text-white uppercase tracking-tight">
175
+ <h3 className="text-lg font-black text-dashboard-text uppercase tracking-tight">
176
176
  {lang.toUpperCase()} - {lang === 'nl' ? 'Dutch' : lang === 'en' ? 'English' : 'Swedish'}
177
177
  </h3>
178
178
  </div>
179
179
  <div className="space-y-4">
180
180
  <div>
181
- <label className="text-xs font-bold text-neutral-500 dark:text-neutral-400 uppercase tracking-widest block mb-2">
181
+ <label className="text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest block mb-2">
182
182
  Email Subject
183
183
  </label>
184
184
  <input
@@ -190,7 +190,7 @@ export function SettingsView({ siteId, locale }: SettingsViewProps) {
190
190
  />
191
191
  </div>
192
192
  <div>
193
- <label className="text-xs font-bold text-neutral-500 dark:text-neutral-400 uppercase tracking-widest block mb-2">
193
+ <label className="text-xs font-bold text-dashboard-text-secondary uppercase tracking-widest block mb-2">
194
194
  Email Message
195
195
  </label>
196
196
  <textarea
@@ -200,7 +200,7 @@ export function SettingsView({ siteId, locale }: SettingsViewProps) {
200
200
  rows={8}
201
201
  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"
202
202
  />
203
- <p className="text-[10px] text-neutral-500 dark:text-neutral-400 mt-2">
203
+ <p className="text-[10px] text-dashboard-text-secondary mt-2">
204
204
  Supports markdown: **bold**, • bullet points, line breaks
205
205
  </p>
206
206
  </div>
@@ -103,22 +103,22 @@ export function SubscribersView({ siteId, locale }: SubscribersViewProps) {
103
103
  };
104
104
 
105
105
  return (
106
- <div className="h-full w-full rounded-[2.5rem] bg-dashboard-card text-dashboard-text overflow-y-auto">
107
- <div className="max-w-7xl mx-auto p-8">
106
+ <div className="h-full w-full rounded-[2.5rem] bg-white dark:bg-neutral-900 p-8 overflow-y-auto">
107
+ <div className="max-w-7xl mx-auto">
108
108
  {/* Header */}
109
109
  <div className="flex flex-col md:flex-row md:items-center justify-between gap-6 mb-8">
110
110
  <div>
111
- <h1 className="text-3xl font-black text-neutral-950 dark:text-white uppercase tracking-tighter mb-2">
111
+ <h1 className="text-3xl font-black text-dashboard-text uppercase tracking-tighter mb-2">
112
112
  Newsletter Subscribers
113
113
  </h1>
114
- <p className="text-sm text-neutral-500 dark:text-neutral-400">
114
+ <p className="text-sm text-dashboard-text-secondary">
115
115
  Manage your newsletter subscribers and their preferences
116
116
  </p>
117
117
  </div>
118
118
 
119
119
  <div className="flex items-center gap-3">
120
120
  {/* Language Filter */}
121
- <div className="flex items-center bg-dashboard-sidebar border border-dashboard-border rounded-xl px-4 py-2.5 shadow-sm">
121
+ <div className="flex items-center bg-dashboard-bg border border-dashboard-border rounded-xl px-4 py-2.5">
122
122
  <Filter size={16} className="text-primary mr-2" />
123
123
  <select
124
124
  value={filter}
@@ -136,7 +136,7 @@ export function SubscribersView({ siteId, locale }: SubscribersViewProps) {
136
136
  <button
137
137
  onClick={handleCopyEmails}
138
138
  disabled={filteredSubscribers.length === 0}
139
- className="inline-flex items-center gap-2 px-6 py-3 rounded-full text-[10px] font-black uppercase tracking-widest transition-colors shadow-lg disabled:opacity-30 disabled:cursor-not-allowed bg-primary text-white hover:bg-primary/90"
139
+ 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"
140
140
  >
141
141
  {copyStatus ? (
142
142
  <>
@@ -155,51 +155,51 @@ export function SubscribersView({ siteId, locale }: SubscribersViewProps) {
155
155
 
156
156
  {/* Stats */}
157
157
  <div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
158
- <div className="bg-dashboard-sidebar p-6 rounded-3xl border border-dashboard-border">
158
+ <div className="bg-dashboard-bg p-6 rounded-3xl border border-dashboard-border">
159
159
  <div className="flex items-center gap-3 mb-2">
160
160
  <Users size={20} className="text-primary" />
161
- <span className="text-[10px] uppercase tracking-widest text-neutral-500 dark:text-neutral-400 font-black">
161
+ <span className="text-[10px] uppercase tracking-widest text-dashboard-text-secondary font-black">
162
162
  Total Subscribers
163
163
  </span>
164
164
  </div>
165
- <p className="text-3xl font-black text-neutral-950 dark:text-white">
165
+ <p className="text-3xl font-black text-dashboard-text">
166
166
  {subscribers.length}
167
167
  </p>
168
168
  </div>
169
- <div className="bg-dashboard-sidebar p-6 rounded-3xl border border-dashboard-border">
169
+ <div className="bg-dashboard-bg p-6 rounded-3xl border border-dashboard-border">
170
170
  <div className="flex items-center gap-3 mb-2">
171
171
  <Globe size={20} className="text-primary" />
172
- <span className="text-[10px] uppercase tracking-widest text-neutral-500 dark:text-neutral-400 font-black">
172
+ <span className="text-[10px] uppercase tracking-widest text-dashboard-text-secondary font-black">
173
173
  Languages
174
174
  </span>
175
175
  </div>
176
- <p className="text-3xl font-black text-neutral-950 dark:text-white">
176
+ <p className="text-3xl font-black text-dashboard-text">
177
177
  {languages.length}
178
178
  </p>
179
179
  </div>
180
- <div className="bg-dashboard-sidebar p-6 rounded-3xl border border-dashboard-border">
180
+ <div className="bg-dashboard-bg p-6 rounded-3xl border border-dashboard-border">
181
181
  <div className="flex items-center gap-3 mb-2">
182
182
  <Filter size={20} className="text-primary" />
183
- <span className="text-[10px] uppercase tracking-widest text-neutral-500 dark:text-neutral-400 font-black">
183
+ <span className="text-[10px] uppercase tracking-widest text-dashboard-text-secondary font-black">
184
184
  Filtered
185
185
  </span>
186
186
  </div>
187
- <p className="text-3xl font-black text-neutral-950 dark:text-white">
187
+ <p className="text-3xl font-black text-dashboard-text">
188
188
  {filteredSubscribers.length}
189
189
  </p>
190
190
  </div>
191
191
  </div>
192
192
 
193
193
  {/* Subscribers Table */}
194
- <div className="bg-dashboard-sidebar rounded-3xl border border-dashboard-border shadow-sm overflow-hidden">
194
+ <div className="bg-dashboard-bg rounded-3xl border border-dashboard-border overflow-hidden">
195
195
  {isLoading ? (
196
196
  <div className="flex items-center justify-center py-20">
197
197
  <div className="w-8 h-8 border-4 border-primary/20 border-t-primary rounded-full animate-spin" />
198
198
  </div>
199
199
  ) : filteredSubscribers.length === 0 ? (
200
200
  <div className="py-24 text-center">
201
- <Users size={64} className="mx-auto text-neutral-300 dark:text-neutral-700 mb-4" />
202
- <p className="text-neutral-500 dark:text-neutral-400 font-serif italic text-lg">
201
+ <Users size={64} className="mx-auto text-dashboard-text-secondary mb-4" />
202
+ <p className="text-dashboard-text-secondary font-serif italic text-lg">
203
203
  {filter === 'all' ? 'No subscribers yet.' : `No subscribers found for ${filter.toUpperCase()}.`}
204
204
  </p>
205
205
  </div>
@@ -235,7 +235,7 @@ export function SubscribersView({ siteId, locale }: SubscribersViewProps) {
235
235
  {subscriber.language}
236
236
  </span>
237
237
  </td>
238
- <td className="px-8 py-5 text-right text-xs text-neutral-500 dark:text-neutral-400 font-medium">
238
+ <td className="px-8 py-5 text-right text-xs text-dashboard-text-secondary font-medium">
239
239
  <div className="flex items-center justify-end gap-2">
240
240
  <Calendar size={14} />
241
241
  {formatDate(subscriber.subscribedAt)}
@@ -245,7 +245,7 @@ export function SubscribersView({ siteId, locale }: SubscribersViewProps) {
245
245
  <button
246
246
  onClick={() => handleDeleteSubscriber(subscriber.email)}
247
247
  disabled={deleteStatus === subscriber.email}
248
- className="p-2.5 rounded-full text-neutral-400 dark:text-neutral-500 hover:text-red-500 dark:hover:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/20 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
248
+ 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"
249
249
  title="Remove subscriber"
250
250
  >
251
251
  {deleteStatus === subscriber.email ? (