strapi-plugin-magic-mail 2.2.4 → 2.2.5

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 (70) hide show
  1. package/dist/server/index.js +1 -1
  2. package/dist/server/index.mjs +1 -1
  3. package/package.json +1 -3
  4. package/admin/jsconfig.json +0 -10
  5. package/admin/src/components/AddAccountModal.jsx +0 -1943
  6. package/admin/src/components/Initializer.jsx +0 -14
  7. package/admin/src/components/LicenseGuard.jsx +0 -475
  8. package/admin/src/components/PluginIcon.jsx +0 -5
  9. package/admin/src/hooks/useAuthRefresh.js +0 -44
  10. package/admin/src/hooks/useLicense.js +0 -158
  11. package/admin/src/index.js +0 -87
  12. package/admin/src/pages/Analytics.jsx +0 -762
  13. package/admin/src/pages/App.jsx +0 -111
  14. package/admin/src/pages/EmailDesigner/EditorPage.jsx +0 -1424
  15. package/admin/src/pages/EmailDesigner/TemplateList.jsx +0 -1807
  16. package/admin/src/pages/HomePage.jsx +0 -1170
  17. package/admin/src/pages/LicensePage.jsx +0 -430
  18. package/admin/src/pages/RoutingRules.jsx +0 -1141
  19. package/admin/src/pages/Settings.jsx +0 -603
  20. package/admin/src/pluginId.js +0 -3
  21. package/admin/src/translations/de.json +0 -71
  22. package/admin/src/translations/en.json +0 -70
  23. package/admin/src/translations/es.json +0 -71
  24. package/admin/src/translations/fr.json +0 -71
  25. package/admin/src/translations/pt.json +0 -71
  26. package/admin/src/utils/fetchWithRetry.js +0 -123
  27. package/admin/src/utils/getTranslation.js +0 -5
  28. package/admin/src/utils/theme.js +0 -85
  29. package/server/jsconfig.json +0 -10
  30. package/server/src/bootstrap.js +0 -157
  31. package/server/src/config/features.js +0 -260
  32. package/server/src/config/index.js +0 -9
  33. package/server/src/content-types/email-account/schema.json +0 -93
  34. package/server/src/content-types/email-event/index.js +0 -8
  35. package/server/src/content-types/email-event/schema.json +0 -57
  36. package/server/src/content-types/email-link/index.js +0 -8
  37. package/server/src/content-types/email-link/schema.json +0 -49
  38. package/server/src/content-types/email-log/index.js +0 -8
  39. package/server/src/content-types/email-log/schema.json +0 -106
  40. package/server/src/content-types/email-template/schema.json +0 -74
  41. package/server/src/content-types/email-template-version/schema.json +0 -60
  42. package/server/src/content-types/index.js +0 -33
  43. package/server/src/content-types/routing-rule/schema.json +0 -59
  44. package/server/src/controllers/accounts.js +0 -229
  45. package/server/src/controllers/analytics.js +0 -361
  46. package/server/src/controllers/controller.js +0 -26
  47. package/server/src/controllers/email-designer.js +0 -474
  48. package/server/src/controllers/index.js +0 -21
  49. package/server/src/controllers/license.js +0 -269
  50. package/server/src/controllers/oauth.js +0 -474
  51. package/server/src/controllers/routing-rules.js +0 -129
  52. package/server/src/controllers/test.js +0 -301
  53. package/server/src/destroy.js +0 -27
  54. package/server/src/index.js +0 -25
  55. package/server/src/middlewares/index.js +0 -3
  56. package/server/src/policies/index.js +0 -3
  57. package/server/src/register.js +0 -5
  58. package/server/src/routes/admin.js +0 -469
  59. package/server/src/routes/content-api.js +0 -37
  60. package/server/src/routes/index.js +0 -9
  61. package/server/src/services/account-manager.js +0 -329
  62. package/server/src/services/analytics.js +0 -512
  63. package/server/src/services/email-designer.js +0 -717
  64. package/server/src/services/email-router.js +0 -1446
  65. package/server/src/services/index.js +0 -17
  66. package/server/src/services/license-guard.js +0 -423
  67. package/server/src/services/oauth.js +0 -515
  68. package/server/src/services/service.js +0 -7
  69. package/server/src/utils/encryption.js +0 -81
  70. package/server/src/utils/logger.js +0 -84
@@ -1,762 +0,0 @@
1
- import { useState, useEffect } from 'react';
2
- import { useFetchClient, useNotification } from '@strapi/strapi/admin';
3
- import { useAuthRefresh } from '../hooks/useAuthRefresh';
4
- import { useLicense } from '../hooks/useLicense';
5
- import styled, { keyframes, css } from 'styled-components';
6
- import {
7
- Box,
8
- Button,
9
- Flex,
10
- Typography,
11
- Loader,
12
- TextInput,
13
- Tabs,
14
- Modal,
15
- } from '@strapi/design-system';
16
- import { Table, Thead, Tbody, Tr, Th, Td } from '@strapi/design-system';
17
- import {
18
- ChartBarIcon,
19
- EnvelopeIcon,
20
- EnvelopeOpenIcon,
21
- CursorArrowRaysIcon,
22
- ExclamationTriangleIcon,
23
- ClockIcon,
24
- CheckCircleIcon,
25
- XCircleIcon,
26
- MagnifyingGlassIcon,
27
- TrashIcon,
28
- } from '@heroicons/react/24/outline';
29
-
30
- // ================ THEME (kopiert von TemplateList) ================
31
- const theme = {
32
- colors: {
33
- primary: { 50: '#F0F9FF', 100: '#E0F2FE', 500: '#0EA5E9', 600: '#0284C7', 700: '#0369A1' },
34
- secondary: { 50: '#F5F3FF', 100: '#EDE9FE', 500: '#A855F7', 600: '#9333EA' },
35
- success: { 100: '#DCFCE7', 500: '#22C55E', 600: '#16A34A', 700: '#15803D' },
36
- warning: { 100: '#FEF3C7', 500: '#F59E0B', 600: '#D97706' },
37
- danger: { 100: '#FEE2E2', 500: '#EF4444', 600: '#DC2626' },
38
- neutral: { 0: '#FFFFFF', 50: '#F9FAFB', 100: '#F3F4F6', 200: '#E5E7EB', 600: '#4B5563', 700: '#374151', 800: '#1F2937' }
39
- },
40
- shadows: {
41
- sm: '0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1)',
42
- md: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1)',
43
- lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1)',
44
- xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1)',
45
- },
46
- transitions: { fast: '150ms cubic-bezier(0.4, 0, 0.2, 1)', normal: '300ms cubic-bezier(0.4, 0, 0.2, 1)', slow: '500ms cubic-bezier(0.4, 0, 0.2, 1)' },
47
- spacing: { xs: '4px', sm: '8px', md: '16px', lg: '24px', xl: '32px', '2xl': '48px' },
48
- borderRadius: { md: '8px', lg: '12px', xl: '16px' }
49
- };
50
-
51
- // ================ ANIMATIONS ================
52
- const fadeIn = keyframes`
53
- from { opacity: 0; transform: translateY(10px); }
54
- to { opacity: 1; transform: translateY(0); }
55
- `;
56
-
57
- const shimmer = keyframes`
58
- 0% { background-position: -200% 0; }
59
- 100% { background-position: 200% 0; }
60
- `;
61
-
62
- const float = keyframes`
63
- 0%, 100% { transform: translateY(0px); }
64
- 50% { transform: translateY(-5px); }
65
- `;
66
-
67
- // ================ RESPONSIVE BREAKPOINTS ================
68
- const breakpoints = {
69
- mobile: '768px',
70
- tablet: '1024px',
71
- };
72
-
73
- // ================ STYLED COMPONENTS (kopiert von TemplateList) ================
74
- const Container = styled(Box)`
75
- ${css`animation: ${fadeIn} ${theme.transitions.slow};`}
76
- min-height: 100vh;
77
- max-width: 1440px;
78
- margin: 0 auto;
79
- padding: ${theme.spacing.xl} ${theme.spacing.lg} 0;
80
-
81
- @media screen and (max-width: ${breakpoints.mobile}) {
82
- padding: 16px 12px 0;
83
- }
84
- `;
85
-
86
- const Header = styled(Box)`
87
- background: linear-gradient(135deg,
88
- ${theme.colors.secondary[600]} 0%,
89
- ${theme.colors.primary[600]} 100%
90
- );
91
- border-radius: ${theme.borderRadius.xl};
92
- padding: ${theme.spacing.xl} ${theme.spacing['2xl']};
93
- margin-bottom: ${theme.spacing.xl};
94
- position: relative;
95
- overflow: hidden;
96
- box-shadow: ${theme.shadows.xl};
97
-
98
- @media screen and (max-width: ${breakpoints.mobile}) {
99
- padding: 24px 20px;
100
- border-radius: 12px;
101
- }
102
-
103
- &::before {
104
- content: '';
105
- position: absolute;
106
- top: 0;
107
- left: -100%;
108
- width: 200%;
109
- height: 100%;
110
- background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.15), transparent);
111
- ${css`animation: ${shimmer} 3s infinite;`}
112
- }
113
-
114
- &::after {
115
- content: '';
116
- position: absolute;
117
- top: 0;
118
- right: 0;
119
- width: 100%;
120
- height: 100%;
121
- background-image: radial-gradient(circle at 20% 80%, transparent 50%, rgba(255, 255, 255, 0.1) 50%);
122
- background-size: 15px 15px;
123
- opacity: 0.3;
124
- }
125
- `;
126
-
127
- const HeaderContent = styled(Flex)`
128
- position: relative;
129
- z-index: 1;
130
- `;
131
-
132
- const Title = styled(Typography)`
133
- color: white;
134
- font-size: 2rem;
135
- font-weight: 700;
136
- letter-spacing: -0.025em;
137
- text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
138
- display: flex;
139
- align-items: center;
140
- gap: ${theme.spacing.sm};
141
-
142
- svg {
143
- width: 28px;
144
- height: 28px;
145
- ${css`animation: ${float} 3s ease-in-out infinite;`}
146
- }
147
-
148
- @media screen and (max-width: ${breakpoints.mobile}) {
149
- font-size: 1.5rem;
150
-
151
- svg {
152
- width: 22px;
153
- height: 22px;
154
- }
155
- }
156
- `;
157
-
158
- const Subtitle = styled(Typography)`
159
- color: rgba(255, 255, 255, 0.95);
160
- font-size: 0.95rem;
161
- font-weight: 400;
162
- margin-top: ${theme.spacing.xs};
163
- letter-spacing: 0.01em;
164
-
165
- @media screen and (max-width: ${breakpoints.mobile}) {
166
- font-size: 0.85rem;
167
- }
168
- `;
169
-
170
- const StatsGrid = styled.div`
171
- margin-bottom: ${theme.spacing.xl};
172
- display: grid;
173
- grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
174
- gap: ${theme.spacing.lg};
175
- justify-content: center;
176
- max-width: 1200px;
177
- margin-left: auto;
178
- margin-right: auto;
179
-
180
- @media screen and (max-width: ${breakpoints.mobile}) {
181
- grid-template-columns: repeat(2, 1fr);
182
- gap: 12px;
183
- margin-bottom: 24px;
184
- }
185
- `;
186
-
187
- const StatCard = styled(Box)`
188
- background: ${props => props.theme.colors.neutral0};
189
- border-radius: ${theme.borderRadius.lg};
190
- padding: 28px ${theme.spacing.lg};
191
- position: relative;
192
- overflow: hidden;
193
- transition: all ${theme.transitions.normal};
194
- ${css`animation: ${fadeIn} ${theme.transitions.slow} backwards;`}
195
- animation-delay: ${props => props.$delay || '0s'};
196
- box-shadow: ${theme.shadows.sm};
197
- border: 1px solid ${props => props.theme.colors.neutral200};
198
- min-width: 200px;
199
- flex: 1;
200
- text-align: center;
201
- display: flex;
202
- flex-direction: column;
203
- align-items: center;
204
- justify-content: center;
205
-
206
- @media screen and (max-width: ${breakpoints.mobile}) {
207
- min-width: unset;
208
- padding: 20px 12px;
209
-
210
- &:hover {
211
- transform: none;
212
- }
213
- }
214
-
215
- &:hover {
216
- transform: translateY(-6px);
217
- box-shadow: ${theme.shadows.xl};
218
- border-color: ${props => props.$color || theme.colors.primary[500]};
219
-
220
- .stat-icon {
221
- transform: scale(1.15) rotate(5deg);
222
- }
223
-
224
- .stat-value {
225
- transform: scale(1.08);
226
- color: ${props => props.$color || theme.colors.primary[600]};
227
- }
228
- }
229
- `;
230
-
231
- const StatIcon = styled(Box)`
232
- width: 68px;
233
- height: 68px;
234
- border-radius: ${theme.borderRadius.lg};
235
- display: flex;
236
- align-items: center;
237
- justify-content: center;
238
- margin-bottom: ${theme.spacing.md};
239
- background: ${props => props.$bg || theme.colors.primary[100]};
240
- transition: all ${theme.transitions.normal};
241
-
242
- svg {
243
- width: 32px;
244
- height: 32px;
245
- color: ${props => props.$color || theme.colors.primary[600]};
246
- }
247
-
248
- @media screen and (max-width: ${breakpoints.mobile}) {
249
- width: 56px;
250
- height: 56px;
251
- margin-bottom: 12px;
252
-
253
- svg {
254
- width: 26px;
255
- height: 26px;
256
- }
257
- }
258
- `;
259
-
260
- const StatValue = styled(Typography)`
261
- font-size: 2.25rem;
262
- font-weight: 700;
263
- color: ${props => props.theme.colors.neutral800};
264
- transition: all ${theme.transitions.normal};
265
- line-height: 1;
266
- margin-bottom: ${theme.spacing.xs};
267
-
268
- @media screen and (max-width: ${breakpoints.mobile}) {
269
- font-size: 1.75rem;
270
- }
271
- `;
272
-
273
- const StatLabel = styled(Typography)`
274
- font-size: 0.875rem;
275
- color: ${props => props.theme.colors.neutral600};
276
- font-weight: 500;
277
- text-transform: uppercase;
278
- letter-spacing: 0.05em;
279
-
280
- @media screen and (max-width: ${breakpoints.mobile}) {
281
- font-size: 0.75rem;
282
- }
283
- `;
284
-
285
- const FilterBar = styled(Box)`
286
- background: ${props => props.theme.colors.neutral0};
287
- border-radius: ${theme.borderRadius.lg};
288
- padding: ${theme.spacing.lg} ${theme.spacing.xl};
289
- margin-bottom: ${theme.spacing.lg};
290
- box-shadow: ${theme.shadows.sm};
291
- border: 1px solid ${props => props.theme.colors.neutral200};
292
- `;
293
-
294
- const StyledTable = styled(Table)`
295
- thead {
296
- background: ${props => props.theme.colors.neutral100};
297
- border-bottom: 2px solid ${props => props.theme.colors.neutral200};
298
-
299
- th {
300
- font-weight: 600;
301
- color: ${props => props.theme.colors.neutral800};
302
- font-size: 0.875rem;
303
- text-transform: uppercase;
304
- letter-spacing: 0.025em;
305
- padding: ${theme.spacing.lg} ${theme.spacing.lg};
306
- }
307
- }
308
-
309
- tbody tr {
310
- transition: all ${theme.transitions.fast};
311
- border-bottom: 1px solid ${props => props.theme.colors.neutral150};
312
-
313
- &:last-child {
314
- border-bottom: none;
315
- }
316
-
317
- &:hover {
318
- background: ${props => props.theme.colors.primary100};
319
- }
320
-
321
- td {
322
- padding: ${theme.spacing.lg} ${theme.spacing.lg};
323
- color: ${props => props.theme.colors.neutral800};
324
- vertical-align: middle;
325
- }
326
- }
327
- `;
328
-
329
- const TableContainer = styled(Box)`
330
- background: ${props => props.theme.colors.neutral0};
331
- border-radius: ${theme.borderRadius.lg};
332
- box-shadow: ${theme.shadows.md};
333
- border: 1px solid ${props => props.theme.colors.neutral200};
334
- overflow: hidden;
335
- margin-bottom: ${theme.spacing.xl};
336
- `;
337
-
338
- const EmptyState = styled(Box)`
339
- background: ${props => props.theme.colors.neutral0};
340
- border-radius: ${theme.borderRadius.xl};
341
- border: 2px dashed ${props => props.theme.colors.neutral300};
342
- padding: 80px 32px;
343
- text-align: center;
344
- position: relative;
345
- overflow: hidden;
346
- min-height: 400px;
347
- display: flex;
348
- align-items: center;
349
- justify-content: center;
350
-
351
- &::before {
352
- content: '';
353
- position: absolute;
354
- top: 0;
355
- left: 0;
356
- right: 0;
357
- bottom: 0;
358
- background: linear-gradient(135deg, ${theme.colors.secondary[50]} 0%, ${theme.colors.primary[50]} 100%);
359
- opacity: 0.3;
360
- z-index: 0;
361
- }
362
- `;
363
-
364
- const EmptyContent = styled.div`
365
- position: relative;
366
- z-index: 1;
367
- max-width: 600px;
368
- margin: 0 auto;
369
- `;
370
-
371
- const EmptyIcon = styled.div`
372
- width: 120px;
373
- height: 120px;
374
- margin: 0 auto ${theme.spacing.lg};
375
- border-radius: 50%;
376
- background: linear-gradient(135deg, ${theme.colors.secondary[100]} 0%, ${theme.colors.primary[100]} 100%);
377
- display: flex;
378
- align-items: center;
379
- justify-content: center;
380
- box-shadow: ${theme.shadows.xl};
381
-
382
- svg {
383
- width: 60px;
384
- height: 60px;
385
- color: ${theme.colors.primary[600]};
386
- }
387
- `;
388
-
389
- const Analytics = () => {
390
- useAuthRefresh();
391
- const { get, del } = useFetchClient();
392
- const { toggleNotification } = useNotification();
393
- const { hasFeature } = useLicense();
394
- const [loading, setLoading] = useState(true);
395
- const [stats, setStats] = useState(null);
396
- const [emailLogs, setEmailLogs] = useState([]);
397
- const [searchTerm, setSearchTerm] = useState('');
398
- const [showClearDialog, setShowClearDialog] = useState(false);
399
- const [isDeleting, setIsDeleting] = useState(false);
400
-
401
- const hasBasicAnalytics = hasFeature('email-logging');
402
-
403
- useEffect(() => {
404
- if (hasBasicAnalytics) {
405
- fetchAnalytics();
406
- fetchEmailLogs();
407
- } else {
408
- setLoading(false);
409
- }
410
- }, [hasBasicAnalytics]);
411
-
412
- const fetchAnalytics = async () => {
413
- try {
414
- const response = await get('/magic-mail/analytics/stats');
415
- console.log('[DEBUG] Analytics response:', response);
416
- console.log('[DEBUG] Stats data:', response.data);
417
-
418
- // Handle the response structure - API returns { success: true, data: {...} }
419
- const statsData = response.data?.data || response.data || {};
420
- console.log('[DEBUG] Stats to set:', statsData);
421
-
422
- setStats(statsData);
423
- } catch (error) {
424
- console.error('Failed to fetch analytics:', error);
425
- console.error('Error details:', error.response?.data);
426
- }
427
- };
428
-
429
- const fetchEmailLogs = async () => {
430
- setLoading(true);
431
- try {
432
- const response = await get('/magic-mail/analytics/emails?_limit=50&_sort=sentAt:DESC');
433
- setEmailLogs(response.data?.data || []);
434
- } catch (error) {
435
- console.error('Failed to fetch email logs:', error);
436
- } finally {
437
- setLoading(false);
438
- }
439
- };
440
-
441
- const handleClearAll = async () => {
442
- setIsDeleting(true);
443
- try {
444
- const response = await del('/magic-mail/analytics/emails');
445
-
446
- toggleNotification({
447
- type: 'success',
448
- message: response.data?.message || 'All email logs cleared successfully',
449
- });
450
-
451
- // Refresh data
452
- await fetchAnalytics();
453
- await fetchEmailLogs();
454
- setShowClearDialog(false);
455
- } catch (error) {
456
- console.error('Failed to clear email logs:', error);
457
- toggleNotification({
458
- type: 'danger',
459
- message: 'Failed to clear email logs',
460
- });
461
- } finally {
462
- setIsDeleting(false);
463
- }
464
- };
465
-
466
- const formatDate = (dateString) => {
467
- if (!dateString) return '-';
468
- return new Date(dateString).toLocaleString('en-US', {
469
- year: 'numeric',
470
- month: 'short',
471
- day: 'numeric',
472
- hour: '2-digit',
473
- minute: '2-digit',
474
- });
475
- };
476
-
477
- const filteredLogs = emailLogs.filter(log =>
478
- log.recipient?.toLowerCase().includes(searchTerm.toLowerCase()) ||
479
- log.subject?.toLowerCase().includes(searchTerm.toLowerCase()) ||
480
- log.templateName?.toLowerCase().includes(searchTerm.toLowerCase())
481
- );
482
-
483
- if (!hasBasicAnalytics) {
484
- return (
485
- <Container>
486
- <Header>
487
- <HeaderContent justifyContent="center" alignItems="center">
488
- <div style={{ textAlign: 'center' }}>
489
- <Title variant="alpha">
490
- <ChartBarIcon />
491
- Email Analytics
492
- </Title>
493
- <Subtitle variant="epsilon">
494
- Upgrade to Premium to unlock detailed email analytics and tracking
495
- </Subtitle>
496
- </div>
497
- </HeaderContent>
498
- </Header>
499
-
500
- <EmptyState>
501
- <EmptyContent>
502
- <EmptyIcon>
503
- <ChartBarIcon />
504
- </EmptyIcon>
505
- <Typography variant="delta" fontWeight="bold" style={{ marginBottom: '12px', display: 'block' }}>
506
- Analytics Available in Premium
507
- </Typography>
508
- <Typography variant="omega" textColor="neutral600" style={{ marginBottom: '32px', lineHeight: '1.6', display: 'block' }}>
509
- Upgrade to Premium to unlock email analytics, tracking, open rates, click rates, and detailed reports about your email campaigns.
510
- </Typography>
511
- <Button
512
- onClick={() => window.location.href = '/admin/settings/magic-mail/upgrade'}
513
- variant="default"
514
- style={{
515
- background: 'linear-gradient(135deg, #8B5CF6, #7C3AED)',
516
- color: 'white',
517
- border: 'none',
518
- padding: '12px 24px',
519
- fontSize: '15px',
520
- fontWeight: '600',
521
- }}
522
- >
523
- View Upgrade Plans
524
- </Button>
525
- </EmptyContent>
526
- </EmptyState>
527
- </Container>
528
- );
529
- }
530
-
531
- if (loading) {
532
- return (
533
- <Container>
534
- <Flex justifyContent="center" alignItems="center" style={{ minHeight: '400px' }}>
535
- <Loader>Loading analytics...</Loader>
536
- </Flex>
537
- </Container>
538
- );
539
- }
540
-
541
- return (
542
- <Container>
543
- <Header>
544
- <HeaderContent justifyContent="flex-start" alignItems="center">
545
- <div>
546
- <Title variant="alpha">
547
- <ChartBarIcon />
548
- Email Analytics
549
- </Title>
550
- <Subtitle variant="epsilon">
551
- Track your email performance and engagement
552
- </Subtitle>
553
- </div>
554
- </HeaderContent>
555
- </Header>
556
-
557
- {/* Stats Cards */}
558
- <StatsGrid>
559
- <StatCard $delay="0.1s" $color={theme.colors.primary[500]}>
560
- <StatIcon className="stat-icon" $bg={theme.colors.primary[100]} $color={theme.colors.primary[600]}>
561
- <EnvelopeIcon />
562
- </StatIcon>
563
- <StatValue className="stat-value">{stats?.totalSent || 0}</StatValue>
564
- <StatLabel>Total Sent</StatLabel>
565
- </StatCard>
566
-
567
- <StatCard $delay="0.2s" $color={theme.colors.success[500]}>
568
- <StatIcon className="stat-icon" $bg={theme.colors.success[100]} $color={theme.colors.success[600]}>
569
- <EnvelopeOpenIcon />
570
- </StatIcon>
571
- <StatValue className="stat-value">{stats?.totalOpened || 0}</StatValue>
572
- <StatLabel>Opened</StatLabel>
573
- </StatCard>
574
-
575
- <StatCard $delay="0.3s" $color={theme.colors.primary[500]}>
576
- <StatIcon className="stat-icon" $bg={theme.colors.primary[100]} $color={theme.colors.primary[600]}>
577
- <CursorArrowRaysIcon />
578
- </StatIcon>
579
- <StatValue className="stat-value">{stats?.totalClicked || 0}</StatValue>
580
- <StatLabel>Clicked</StatLabel>
581
- </StatCard>
582
-
583
- <StatCard $delay="0.4s" $color={theme.colors.danger[500]}>
584
- <StatIcon className="stat-icon" $bg={theme.colors.danger[100]} $color={theme.colors.danger[600]}>
585
- <ExclamationTriangleIcon />
586
- </StatIcon>
587
- <StatValue className="stat-value">{stats?.totalBounced || 0}</StatValue>
588
- <StatLabel>Bounced</StatLabel>
589
- </StatCard>
590
- </StatsGrid>
591
-
592
- {/* Filter Bar */}
593
- <FilterBar>
594
- <Flex justifyContent="space-between" alignItems="center">
595
- <Typography variant="omega" fontWeight="semiBold" textColor="neutral700">
596
- Recent Emails ({filteredLogs.length})
597
- </Typography>
598
- <Flex gap={2}>
599
- <TextInput
600
- placeholder="Search emails..."
601
- value={searchTerm}
602
- onChange={(e) => setSearchTerm(e.target.value)}
603
- startAction={<MagnifyingGlassIcon style={{ width: 16, height: 16 }} />}
604
- style={{ maxWidth: '300px' }}
605
- />
606
- {emailLogs.length > 0 && (
607
- <Button
608
- variant="danger-light"
609
- startIcon={<TrashIcon />}
610
- onClick={() => setShowClearDialog(true)}
611
- disabled={isDeleting}
612
- >
613
- Clear All
614
- </Button>
615
- )}
616
- </Flex>
617
- </Flex>
618
- </FilterBar>
619
-
620
- {/* Email Logs Table */}
621
- {filteredLogs.length === 0 ? (
622
- <EmptyState>
623
- <EmptyContent>
624
- <EmptyIcon>
625
- <EnvelopeIcon />
626
- </EmptyIcon>
627
- <Typography variant="delta" fontWeight="bold" style={{ marginBottom: '12px', display: 'block' }}>
628
- {searchTerm ? 'No emails found' : 'No emails sent yet'}
629
- </Typography>
630
- <Typography variant="omega" textColor="neutral600" style={{ lineHeight: '1.6', display: 'block' }}>
631
- {searchTerm
632
- ? 'Try adjusting your search terms'
633
- : 'Send your first email to see analytics and tracking information here!'}
634
- </Typography>
635
- </EmptyContent>
636
- </EmptyState>
637
- ) : (
638
- <TableContainer>
639
- <Box style={{ overflowX: 'auto' }}>
640
- <StyledTable colCount={6} rowCount={filteredLogs.length}>
641
- <Thead>
642
- <Tr>
643
- <Th><Typography variant="sigma">Recipient</Typography></Th>
644
- <Th><Typography variant="sigma">Subject</Typography></Th>
645
- <Th><Typography variant="sigma">Template</Typography></Th>
646
- <Th><Typography variant="sigma">Sent At</Typography></Th>
647
- <Th><Typography variant="sigma">Opened</Typography></Th>
648
- <Th><Typography variant="sigma">Clicked</Typography></Th>
649
- </Tr>
650
- </Thead>
651
- <Tbody>
652
- {filteredLogs.map((log) => (
653
- <Tr key={log.id}>
654
- <Td>
655
- <Typography variant="omega" fontWeight="semiBold">
656
- {log.recipient}
657
- </Typography>
658
- {log.recipientName && (
659
- <Typography variant="pi" textColor="neutral600">
660
- {log.recipientName}
661
- </Typography>
662
- )}
663
- </Td>
664
- <Td>
665
- <Typography variant="omega">{log.subject || '-'}</Typography>
666
- </Td>
667
- <Td>
668
- <Typography variant="omega" textColor="neutral600">
669
- {log.templateName || '-'}
670
- </Typography>
671
- </Td>
672
- <Td>
673
- <Typography variant="pi" textColor="neutral600">
674
- {formatDate(log.sentAt)}
675
- </Typography>
676
- </Td>
677
- <Td>
678
- {log.openCount > 0 ? (
679
- <Flex alignItems="center" gap={2}>
680
- <CheckCircleIcon style={{ width: 16, height: 16, color: theme.colors.success[600] }} />
681
- <Typography variant="pi" fontWeight="semiBold" style={{ color: theme.colors.success[600] }}>
682
- {log.openCount} {log.openCount === 1 ? 'time' : 'times'}
683
- </Typography>
684
- </Flex>
685
- ) : (
686
- <Flex alignItems="center" gap={1}>
687
- <XCircleIcon style={{ width: 16, height: 16, color: '#9CA3AF' }} />
688
- <Typography variant="pi" textColor="neutral600">
689
- No
690
- </Typography>
691
- </Flex>
692
- )}
693
- </Td>
694
- <Td>
695
- {log.clickCount > 0 ? (
696
- <Flex alignItems="center" gap={2}>
697
- <CheckCircleIcon style={{ width: 16, height: 16, color: theme.colors.primary[600] }} />
698
- <Typography variant="pi" fontWeight="semiBold" style={{ color: theme.colors.primary[600] }}>
699
- {log.clickCount} {log.clickCount === 1 ? 'time' : 'times'}
700
- </Typography>
701
- </Flex>
702
- ) : (
703
- <Flex alignItems="center" gap={1}>
704
- <XCircleIcon style={{ width: 16, height: 16, color: '#9CA3AF' }} />
705
- <Typography variant="pi" textColor="neutral600">
706
- No
707
- </Typography>
708
- </Flex>
709
- )}
710
- </Td>
711
- </Tr>
712
- ))}
713
- </Tbody>
714
- </StyledTable>
715
- </Box>
716
- </TableContainer>
717
- )}
718
-
719
- {/* Clear All Confirmation Dialog */}
720
- <Modal.Root open={showClearDialog} onOpenChange={setShowClearDialog}>
721
- <Modal.Content>
722
- <Modal.Header>
723
- <Typography variant="beta" fontWeight="bold">
724
- Clear All Email Logs?
725
- </Typography>
726
- </Modal.Header>
727
- <Modal.Body>
728
- <Flex direction="column" gap={4}>
729
- <Typography>
730
- Are you sure you want to delete all email logs? This action cannot be undone.
731
- </Typography>
732
- <Typography variant="pi" textColor="neutral600">
733
- This will permanently delete {emailLogs.length} email log(s) and all associated tracking data.
734
- </Typography>
735
- </Flex>
736
- </Modal.Body>
737
- <Modal.Footer>
738
- <Flex justifyContent="flex-end" gap={2}>
739
- <Button
740
- variant="tertiary"
741
- onClick={() => setShowClearDialog(false)}
742
- disabled={isDeleting}
743
- >
744
- Cancel
745
- </Button>
746
- <Button
747
- variant="danger"
748
- onClick={handleClearAll}
749
- loading={isDeleting}
750
- startIcon={<TrashIcon />}
751
- >
752
- Delete All
753
- </Button>
754
- </Flex>
755
- </Modal.Footer>
756
- </Modal.Content>
757
- </Modal.Root>
758
- </Container>
759
- );
760
- };
761
-
762
- export default Analytics;