strapi-plugin-magic-mail 2.2.3 → 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.
- package/dist/server/index.js +1 -1
- package/dist/server/index.mjs +1 -1
- package/package.json +1 -3
- package/admin/jsconfig.json +0 -10
- package/admin/src/components/AddAccountModal.jsx +0 -1943
- package/admin/src/components/Initializer.jsx +0 -14
- package/admin/src/components/LicenseGuard.jsx +0 -475
- package/admin/src/components/PluginIcon.jsx +0 -5
- package/admin/src/hooks/useAuthRefresh.js +0 -44
- package/admin/src/hooks/useLicense.js +0 -158
- package/admin/src/index.js +0 -87
- package/admin/src/pages/Analytics.jsx +0 -762
- package/admin/src/pages/App.jsx +0 -111
- package/admin/src/pages/EmailDesigner/EditorPage.jsx +0 -1424
- package/admin/src/pages/EmailDesigner/TemplateList.jsx +0 -1807
- package/admin/src/pages/HomePage.jsx +0 -1170
- package/admin/src/pages/LicensePage.jsx +0 -430
- package/admin/src/pages/RoutingRules.jsx +0 -1141
- package/admin/src/pages/Settings.jsx +0 -603
- package/admin/src/pluginId.js +0 -3
- package/admin/src/translations/de.json +0 -71
- package/admin/src/translations/en.json +0 -70
- package/admin/src/translations/es.json +0 -71
- package/admin/src/translations/fr.json +0 -71
- package/admin/src/translations/pt.json +0 -71
- package/admin/src/utils/fetchWithRetry.js +0 -123
- package/admin/src/utils/getTranslation.js +0 -5
- package/admin/src/utils/theme.js +0 -85
- package/server/jsconfig.json +0 -10
- package/server/src/bootstrap.js +0 -157
- package/server/src/config/features.js +0 -260
- package/server/src/config/index.js +0 -9
- package/server/src/content-types/email-account/schema.json +0 -93
- package/server/src/content-types/email-event/index.js +0 -8
- package/server/src/content-types/email-event/schema.json +0 -57
- package/server/src/content-types/email-link/index.js +0 -8
- package/server/src/content-types/email-link/schema.json +0 -49
- package/server/src/content-types/email-log/index.js +0 -8
- package/server/src/content-types/email-log/schema.json +0 -106
- package/server/src/content-types/email-template/schema.json +0 -74
- package/server/src/content-types/email-template-version/schema.json +0 -60
- package/server/src/content-types/index.js +0 -33
- package/server/src/content-types/routing-rule/schema.json +0 -59
- package/server/src/controllers/accounts.js +0 -229
- package/server/src/controllers/analytics.js +0 -361
- package/server/src/controllers/controller.js +0 -26
- package/server/src/controllers/email-designer.js +0 -474
- package/server/src/controllers/index.js +0 -21
- package/server/src/controllers/license.js +0 -269
- package/server/src/controllers/oauth.js +0 -474
- package/server/src/controllers/routing-rules.js +0 -129
- package/server/src/controllers/test.js +0 -301
- package/server/src/destroy.js +0 -27
- package/server/src/index.js +0 -25
- package/server/src/middlewares/index.js +0 -3
- package/server/src/policies/index.js +0 -3
- package/server/src/register.js +0 -5
- package/server/src/routes/admin.js +0 -469
- package/server/src/routes/content-api.js +0 -37
- package/server/src/routes/index.js +0 -9
- package/server/src/services/account-manager.js +0 -329
- package/server/src/services/analytics.js +0 -512
- package/server/src/services/email-designer.js +0 -717
- package/server/src/services/email-router.js +0 -1446
- package/server/src/services/index.js +0 -17
- package/server/src/services/license-guard.js +0 -423
- package/server/src/services/oauth.js +0 -515
- package/server/src/services/service.js +0 -7
- package/server/src/utils/encryption.js +0 -81
- 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;
|