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,14 +0,0 @@
|
|
|
1
|
-
import { useEffect, useRef } from 'react';
|
|
2
|
-
import pluginId from '../pluginId';
|
|
3
|
-
|
|
4
|
-
const Initializer = ({ setPlugin }) => {
|
|
5
|
-
const ref = useRef(setPlugin);
|
|
6
|
-
|
|
7
|
-
useEffect(() => {
|
|
8
|
-
ref.current(pluginId);
|
|
9
|
-
}, []);
|
|
10
|
-
|
|
11
|
-
return null;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
export default Initializer;
|
|
@@ -1,475 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect } from 'react';
|
|
2
|
-
import styled, { keyframes } from 'styled-components';
|
|
3
|
-
import {
|
|
4
|
-
Typography,
|
|
5
|
-
Box,
|
|
6
|
-
Flex,
|
|
7
|
-
Button,
|
|
8
|
-
TextInput,
|
|
9
|
-
Loader,
|
|
10
|
-
} from '@strapi/design-system';
|
|
11
|
-
import { useFetchClient, useNotification } from '@strapi/strapi/admin';
|
|
12
|
-
import { CheckIcon, KeyIcon, XMarkIcon } from '@heroicons/react/24/outline';
|
|
13
|
-
import { useNavigate } from 'react-router-dom';
|
|
14
|
-
import { useAuthRefresh } from '../hooks/useAuthRefresh';
|
|
15
|
-
|
|
16
|
-
// Animations
|
|
17
|
-
const fadeIn = keyframes`
|
|
18
|
-
from { opacity: 0; }
|
|
19
|
-
to { opacity: 1; }
|
|
20
|
-
`;
|
|
21
|
-
|
|
22
|
-
const slideUp = keyframes`
|
|
23
|
-
from {
|
|
24
|
-
opacity: 0;
|
|
25
|
-
transform: translateY(30px);
|
|
26
|
-
}
|
|
27
|
-
to {
|
|
28
|
-
opacity: 1;
|
|
29
|
-
transform: translateY(0);
|
|
30
|
-
}
|
|
31
|
-
`;
|
|
32
|
-
|
|
33
|
-
// Styled Components
|
|
34
|
-
const ModalOverlay = styled.div`
|
|
35
|
-
position: fixed;
|
|
36
|
-
top: 0;
|
|
37
|
-
left: 0;
|
|
38
|
-
right: 0;
|
|
39
|
-
bottom: 0;
|
|
40
|
-
background: rgba(4, 28, 47, 0.85);
|
|
41
|
-
backdrop-filter: blur(8px);
|
|
42
|
-
z-index: 9999;
|
|
43
|
-
display: flex;
|
|
44
|
-
align-items: center;
|
|
45
|
-
justify-content: center;
|
|
46
|
-
animation: ${fadeIn} 0.3s ease-out;
|
|
47
|
-
padding: 20px;
|
|
48
|
-
`;
|
|
49
|
-
|
|
50
|
-
const ModalContent = styled(Box)`
|
|
51
|
-
background: white;
|
|
52
|
-
border-radius: 16px;
|
|
53
|
-
width: 100%;
|
|
54
|
-
max-width: 580px;
|
|
55
|
-
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.3);
|
|
56
|
-
animation: ${slideUp} 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
|
57
|
-
overflow: hidden;
|
|
58
|
-
`;
|
|
59
|
-
|
|
60
|
-
const GradientHeader = styled(Box)`
|
|
61
|
-
background: linear-gradient(135deg, #0EA5E9 0%, #A855F7 100%);
|
|
62
|
-
padding: 32px 40px;
|
|
63
|
-
position: relative;
|
|
64
|
-
overflow: hidden;
|
|
65
|
-
|
|
66
|
-
&::before {
|
|
67
|
-
content: '';
|
|
68
|
-
position: absolute;
|
|
69
|
-
top: -50%;
|
|
70
|
-
right: -50%;
|
|
71
|
-
width: 200%;
|
|
72
|
-
height: 200%;
|
|
73
|
-
background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, transparent 70%);
|
|
74
|
-
}
|
|
75
|
-
`;
|
|
76
|
-
|
|
77
|
-
const IconWrapper = styled.div`
|
|
78
|
-
width: 72px;
|
|
79
|
-
height: 72px;
|
|
80
|
-
border-radius: 50%;
|
|
81
|
-
background: rgba(255, 255, 255, 0.2);
|
|
82
|
-
display: flex;
|
|
83
|
-
align-items: center;
|
|
84
|
-
justify-content: center;
|
|
85
|
-
margin: 0 auto 16px;
|
|
86
|
-
backdrop-filter: blur(10px);
|
|
87
|
-
border: 2px solid rgba(255, 255, 255, 0.3);
|
|
88
|
-
|
|
89
|
-
svg {
|
|
90
|
-
width: 36px;
|
|
91
|
-
height: 36px;
|
|
92
|
-
color: white;
|
|
93
|
-
}
|
|
94
|
-
`;
|
|
95
|
-
|
|
96
|
-
const CloseButton = styled.button`
|
|
97
|
-
position: absolute;
|
|
98
|
-
top: 16px;
|
|
99
|
-
right: 16px;
|
|
100
|
-
background: rgba(255, 255, 255, 0.2);
|
|
101
|
-
border: 2px solid rgba(255, 255, 255, 0.3);
|
|
102
|
-
border-radius: 50%;
|
|
103
|
-
width: 36px;
|
|
104
|
-
height: 36px;
|
|
105
|
-
display: flex;
|
|
106
|
-
align-items: center;
|
|
107
|
-
justify-content: center;
|
|
108
|
-
cursor: pointer;
|
|
109
|
-
transition: all 0.2s;
|
|
110
|
-
z-index: 10;
|
|
111
|
-
|
|
112
|
-
svg {
|
|
113
|
-
width: 20px;
|
|
114
|
-
height: 20px;
|
|
115
|
-
color: white;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
&:hover {
|
|
119
|
-
background: rgba(255, 255, 255, 0.3);
|
|
120
|
-
transform: scale(1.1);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
&:active {
|
|
124
|
-
transform: scale(0.95);
|
|
125
|
-
}
|
|
126
|
-
`;
|
|
127
|
-
|
|
128
|
-
const ToggleButton = styled.button`
|
|
129
|
-
background: none;
|
|
130
|
-
border: none;
|
|
131
|
-
color: #0EA5E9;
|
|
132
|
-
font-size: 13px;
|
|
133
|
-
font-weight: 600;
|
|
134
|
-
cursor: pointer;
|
|
135
|
-
padding: 8px 0;
|
|
136
|
-
text-decoration: underline;
|
|
137
|
-
transition: color 0.2s;
|
|
138
|
-
|
|
139
|
-
&:hover {
|
|
140
|
-
color: #A855F7;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
&:disabled {
|
|
144
|
-
opacity: 0.5;
|
|
145
|
-
cursor: not-allowed;
|
|
146
|
-
}
|
|
147
|
-
`;
|
|
148
|
-
|
|
149
|
-
const LicenseGuard = ({ children }) => {
|
|
150
|
-
const { get, post } = useFetchClient();
|
|
151
|
-
const { toggleNotification } = useNotification();
|
|
152
|
-
const navigate = useNavigate();
|
|
153
|
-
useAuthRefresh(); // Initialize token auto-refresh
|
|
154
|
-
|
|
155
|
-
const [isChecking, setIsChecking] = useState(true);
|
|
156
|
-
const [needsLicense, setNeedsLicense] = useState(false);
|
|
157
|
-
const [isCreating, setIsCreating] = useState(false);
|
|
158
|
-
const [useExistingKey, setUseExistingKey] = useState(false);
|
|
159
|
-
const [useAutoCreate, setUseAutoCreate] = useState(true);
|
|
160
|
-
const [existingLicenseKey, setExistingLicenseKey] = useState('');
|
|
161
|
-
const [existingEmail, setExistingEmail] = useState('');
|
|
162
|
-
const [adminUser, setAdminUser] = useState(null);
|
|
163
|
-
|
|
164
|
-
useEffect(() => {
|
|
165
|
-
checkLicenseStatus();
|
|
166
|
-
fetchAdminUser();
|
|
167
|
-
}, []);
|
|
168
|
-
|
|
169
|
-
const fetchAdminUser = async () => {
|
|
170
|
-
try {
|
|
171
|
-
const response = await get('/admin/users/me');
|
|
172
|
-
const userData = response.data?.data || response.data;
|
|
173
|
-
if (userData) {
|
|
174
|
-
setAdminUser(userData);
|
|
175
|
-
}
|
|
176
|
-
} catch (error) {
|
|
177
|
-
console.debug('[MagicMail] Could not fetch admin user');
|
|
178
|
-
}
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
const checkLicenseStatus = async () => {
|
|
182
|
-
setIsChecking(true);
|
|
183
|
-
try {
|
|
184
|
-
const response = await get('/magic-mail/license/status');
|
|
185
|
-
|
|
186
|
-
if (response.data.valid) {
|
|
187
|
-
setNeedsLicense(false);
|
|
188
|
-
} else {
|
|
189
|
-
setNeedsLicense(true);
|
|
190
|
-
}
|
|
191
|
-
} catch (error) {
|
|
192
|
-
console.error('[MagicMail] License check error:', error);
|
|
193
|
-
setNeedsLicense(true);
|
|
194
|
-
} finally {
|
|
195
|
-
setIsChecking(false);
|
|
196
|
-
}
|
|
197
|
-
};
|
|
198
|
-
|
|
199
|
-
const handleAutoCreateLicense = async (e) => {
|
|
200
|
-
e.preventDefault();
|
|
201
|
-
setIsCreating(true);
|
|
202
|
-
|
|
203
|
-
try {
|
|
204
|
-
const response = await post('/magic-mail/license/auto-create', {});
|
|
205
|
-
|
|
206
|
-
if (response.data && response.data.success) {
|
|
207
|
-
toggleNotification({
|
|
208
|
-
type: 'success',
|
|
209
|
-
message: '✅ License created! Reloading...',
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
setNeedsLicense(false);
|
|
213
|
-
|
|
214
|
-
setTimeout(() => {
|
|
215
|
-
window.location.reload();
|
|
216
|
-
}, 500);
|
|
217
|
-
} else {
|
|
218
|
-
throw new Error('Failed to auto-create license');
|
|
219
|
-
}
|
|
220
|
-
} catch (error) {
|
|
221
|
-
console.error('[MagicMail] Error:', error);
|
|
222
|
-
toggleNotification({
|
|
223
|
-
type: 'danger',
|
|
224
|
-
message: 'Failed to create license. Try manual entry.',
|
|
225
|
-
});
|
|
226
|
-
setIsCreating(false);
|
|
227
|
-
setUseAutoCreate(false);
|
|
228
|
-
}
|
|
229
|
-
};
|
|
230
|
-
|
|
231
|
-
const handleValidateExistingKey = async (e) => {
|
|
232
|
-
e.preventDefault();
|
|
233
|
-
|
|
234
|
-
if (!existingLicenseKey.trim() || !existingEmail.trim()) {
|
|
235
|
-
toggleNotification({
|
|
236
|
-
type: 'warning',
|
|
237
|
-
message: 'Please enter both license key and email address',
|
|
238
|
-
});
|
|
239
|
-
return;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
setIsCreating(true);
|
|
243
|
-
|
|
244
|
-
try {
|
|
245
|
-
const response = await post('/magic-mail/license/store-key', {
|
|
246
|
-
licenseKey: existingLicenseKey.trim(),
|
|
247
|
-
email: existingEmail.trim(),
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
if (response.data && response.data.success) {
|
|
251
|
-
toggleNotification({
|
|
252
|
-
type: 'success',
|
|
253
|
-
message: '✅ License activated! Reloading...',
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
setNeedsLicense(false);
|
|
257
|
-
|
|
258
|
-
setTimeout(() => {
|
|
259
|
-
window.location.reload();
|
|
260
|
-
}, 500);
|
|
261
|
-
} else {
|
|
262
|
-
throw new Error('Invalid license');
|
|
263
|
-
}
|
|
264
|
-
} catch (error) {
|
|
265
|
-
toggleNotification({
|
|
266
|
-
type: 'danger',
|
|
267
|
-
message: 'Invalid license key or email address',
|
|
268
|
-
});
|
|
269
|
-
setIsCreating(false);
|
|
270
|
-
}
|
|
271
|
-
};
|
|
272
|
-
|
|
273
|
-
const handleClose = () => {
|
|
274
|
-
navigate('/content-manager');
|
|
275
|
-
};
|
|
276
|
-
|
|
277
|
-
if (isChecking) {
|
|
278
|
-
return (
|
|
279
|
-
<Box padding={8} style={{ textAlign: 'center' }}>
|
|
280
|
-
<Loader>Checking license...</Loader>
|
|
281
|
-
</Box>
|
|
282
|
-
);
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
if (needsLicense) {
|
|
286
|
-
return (
|
|
287
|
-
<ModalOverlay>
|
|
288
|
-
<ModalContent>
|
|
289
|
-
<GradientHeader>
|
|
290
|
-
<CloseButton onClick={handleClose} type="button">
|
|
291
|
-
<XMarkIcon />
|
|
292
|
-
</CloseButton>
|
|
293
|
-
<IconWrapper>
|
|
294
|
-
<KeyIcon />
|
|
295
|
-
</IconWrapper>
|
|
296
|
-
<Box style={{ textAlign: 'center', position: 'relative' }}>
|
|
297
|
-
<Typography
|
|
298
|
-
variant="alpha"
|
|
299
|
-
style={{
|
|
300
|
-
color: 'white',
|
|
301
|
-
fontSize: '24px',
|
|
302
|
-
fontWeight: '700',
|
|
303
|
-
marginBottom: '12px',
|
|
304
|
-
display: 'block',
|
|
305
|
-
}}
|
|
306
|
-
>
|
|
307
|
-
🔐 Activate MagicMail
|
|
308
|
-
</Typography>
|
|
309
|
-
<Typography
|
|
310
|
-
variant="epsilon"
|
|
311
|
-
style={{
|
|
312
|
-
color: 'rgba(255, 255, 255, 0.9)',
|
|
313
|
-
fontSize: '14px',
|
|
314
|
-
display: 'block',
|
|
315
|
-
}}
|
|
316
|
-
>
|
|
317
|
-
{useExistingKey ? 'Enter your existing license key' : 'Create a license to start using the plugin'}
|
|
318
|
-
</Typography>
|
|
319
|
-
</Box>
|
|
320
|
-
</GradientHeader>
|
|
321
|
-
|
|
322
|
-
<form onSubmit={useExistingKey ? handleValidateExistingKey : handleAutoCreateLicense}>
|
|
323
|
-
<Box padding={6} paddingLeft={8} paddingRight={8}>
|
|
324
|
-
<Flex direction="column" gap={5} style={{ width: '100%' }}>
|
|
325
|
-
<Box style={{ textAlign: 'center', width: '100%' }}>
|
|
326
|
-
<ToggleButton
|
|
327
|
-
type="button"
|
|
328
|
-
onClick={() => setUseExistingKey(!useExistingKey)}
|
|
329
|
-
disabled={isCreating}
|
|
330
|
-
>
|
|
331
|
-
{useExistingKey ? '← Create new license' : 'Have a license key? →'}
|
|
332
|
-
</ToggleButton>
|
|
333
|
-
</Box>
|
|
334
|
-
|
|
335
|
-
<Box
|
|
336
|
-
background="primary100"
|
|
337
|
-
padding={4}
|
|
338
|
-
style={{
|
|
339
|
-
borderRadius: '8px',
|
|
340
|
-
border: '2px solid #BAE6FD',
|
|
341
|
-
width: '100%',
|
|
342
|
-
}}
|
|
343
|
-
>
|
|
344
|
-
<Typography variant="omega" style={{ fontSize: '13px', lineHeight: '1.6' }}>
|
|
345
|
-
{useExistingKey
|
|
346
|
-
? '🔑 Enter your email and license key to activate.'
|
|
347
|
-
: adminUser && adminUser.email
|
|
348
|
-
? `✨ Click "Activate" to auto-create a license with your account (${adminUser.email})`
|
|
349
|
-
: '✨ Click "Activate" to auto-create a license with your admin account'
|
|
350
|
-
}
|
|
351
|
-
</Typography>
|
|
352
|
-
</Box>
|
|
353
|
-
|
|
354
|
-
{useExistingKey ? (
|
|
355
|
-
// Existing License Key Input
|
|
356
|
-
<>
|
|
357
|
-
<Box style={{ width: '100%' }}>
|
|
358
|
-
<Typography
|
|
359
|
-
variant="pi"
|
|
360
|
-
fontWeight="bold"
|
|
361
|
-
style={{ marginBottom: '8px', display: 'block' }}
|
|
362
|
-
>
|
|
363
|
-
Email Address *
|
|
364
|
-
</Typography>
|
|
365
|
-
<TextInput
|
|
366
|
-
placeholder="admin@example.com"
|
|
367
|
-
type="email"
|
|
368
|
-
value={existingEmail}
|
|
369
|
-
onChange={(e) => setExistingEmail(e.target.value)}
|
|
370
|
-
required
|
|
371
|
-
disabled={isCreating}
|
|
372
|
-
/>
|
|
373
|
-
<Typography variant="omega" textColor="neutral600" style={{ fontSize: '11px', marginTop: '4px' }}>
|
|
374
|
-
Enter the email address associated with this license
|
|
375
|
-
</Typography>
|
|
376
|
-
</Box>
|
|
377
|
-
|
|
378
|
-
<Box style={{ width: '100%' }}>
|
|
379
|
-
<Typography
|
|
380
|
-
variant="pi"
|
|
381
|
-
fontWeight="bold"
|
|
382
|
-
style={{ marginBottom: '8px', display: 'block' }}
|
|
383
|
-
>
|
|
384
|
-
License Key *
|
|
385
|
-
</Typography>
|
|
386
|
-
<TextInput
|
|
387
|
-
placeholder="MAGIC-MAIL-XXXX-XXXX-XXXX"
|
|
388
|
-
value={existingLicenseKey}
|
|
389
|
-
onChange={(e) => setExistingLicenseKey(e.target.value)}
|
|
390
|
-
required
|
|
391
|
-
disabled={isCreating}
|
|
392
|
-
/>
|
|
393
|
-
<Typography variant="omega" textColor="neutral600" style={{ fontSize: '11px', marginTop: '4px' }}>
|
|
394
|
-
Enter the license key
|
|
395
|
-
</Typography>
|
|
396
|
-
</Box>
|
|
397
|
-
</>
|
|
398
|
-
) : adminUser ? (
|
|
399
|
-
// Auto-create mode - Show user info
|
|
400
|
-
<Box
|
|
401
|
-
background="success100"
|
|
402
|
-
padding={5}
|
|
403
|
-
style={{
|
|
404
|
-
borderRadius: '8px',
|
|
405
|
-
border: '2px solid #DCFCE7',
|
|
406
|
-
textAlign: 'center',
|
|
407
|
-
}}
|
|
408
|
-
>
|
|
409
|
-
<Typography variant="omega" fontWeight="bold" style={{ marginBottom: '12px', display: 'block' }}>
|
|
410
|
-
Ready to activate with your account:
|
|
411
|
-
</Typography>
|
|
412
|
-
<Typography variant="pi" style={{ marginBottom: '4px', display: 'block' }}>
|
|
413
|
-
👤 {adminUser.firstname || 'Admin'} {adminUser.lastname || 'User'}
|
|
414
|
-
</Typography>
|
|
415
|
-
<Typography variant="pi" textColor="neutral600">
|
|
416
|
-
📧 {adminUser.email || 'Loading...'}
|
|
417
|
-
</Typography>
|
|
418
|
-
</Box>
|
|
419
|
-
) : (
|
|
420
|
-
<Box padding={4} background="neutral100" hasRadius style={{ textAlign: 'center' }}>
|
|
421
|
-
<Loader small />
|
|
422
|
-
<Typography variant="pi" marginTop={2}>Loading admin user data...</Typography>
|
|
423
|
-
</Box>
|
|
424
|
-
)}
|
|
425
|
-
|
|
426
|
-
<Flex gap={3} justifyContent="center" style={{ marginTop: '16px' }}>
|
|
427
|
-
{useExistingKey ? (
|
|
428
|
-
<Button
|
|
429
|
-
type="submit"
|
|
430
|
-
size="L"
|
|
431
|
-
startIcon={<CheckIcon style={{ width: 20, height: 20 }} />}
|
|
432
|
-
loading={isCreating}
|
|
433
|
-
disabled={isCreating || !existingLicenseKey.trim() || !existingEmail.trim()}
|
|
434
|
-
style={{
|
|
435
|
-
background: 'linear-gradient(135deg, #0EA5E9 0%, #A855F7 100%)',
|
|
436
|
-
color: 'white',
|
|
437
|
-
fontWeight: '600',
|
|
438
|
-
border: 'none',
|
|
439
|
-
boxShadow: '0 4px 12px rgba(14, 165, 233, 0.4)',
|
|
440
|
-
}}
|
|
441
|
-
>
|
|
442
|
-
Validate License
|
|
443
|
-
</Button>
|
|
444
|
-
) : (
|
|
445
|
-
<Button
|
|
446
|
-
type="submit"
|
|
447
|
-
size="L"
|
|
448
|
-
startIcon={<CheckIcon style={{ width: 20, height: 20 }} />}
|
|
449
|
-
loading={isCreating}
|
|
450
|
-
disabled={isCreating || !adminUser}
|
|
451
|
-
style={{
|
|
452
|
-
background: 'linear-gradient(135deg, #0EA5E9 0%, #A855F7 100%)',
|
|
453
|
-
color: 'white',
|
|
454
|
-
fontWeight: '600',
|
|
455
|
-
border: 'none',
|
|
456
|
-
boxShadow: '0 4px 12px rgba(14, 165, 233, 0.4)',
|
|
457
|
-
}}
|
|
458
|
-
>
|
|
459
|
-
Activate License
|
|
460
|
-
</Button>
|
|
461
|
-
)}
|
|
462
|
-
</Flex>
|
|
463
|
-
</Flex>
|
|
464
|
-
</Box>
|
|
465
|
-
</form>
|
|
466
|
-
</ModalContent>
|
|
467
|
-
</ModalOverlay>
|
|
468
|
-
);
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
return <>{children}</>;
|
|
472
|
-
};
|
|
473
|
-
|
|
474
|
-
export default LicenseGuard;
|
|
475
|
-
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { useEffect, useRef } from 'react';
|
|
2
|
-
import { useFetchClient } from '@strapi/strapi/admin';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Hook to handle JWT token refresh for admin panel
|
|
6
|
-
* Automatically refreshes token before expiration (every 4 minutes)
|
|
7
|
-
* Also handles 401 responses with automatic token refresh
|
|
8
|
-
*/
|
|
9
|
-
export const useAuthRefresh = () => {
|
|
10
|
-
const { get } = useFetchClient();
|
|
11
|
-
const intervalRef = useRef(null);
|
|
12
|
-
|
|
13
|
-
useEffect(() => {
|
|
14
|
-
// Set up auto-refresh every 4 minutes (before 5 min expiration)
|
|
15
|
-
intervalRef.current = setInterval(async () => {
|
|
16
|
-
try {
|
|
17
|
-
// Refresh token by hitting a protected endpoint
|
|
18
|
-
await get('/admin/users/me');
|
|
19
|
-
console.debug('[Auth Refresh] Token refreshed successfully');
|
|
20
|
-
} catch (error) {
|
|
21
|
-
// If refresh fails, log but don't break
|
|
22
|
-
console.debug('[Auth Refresh] Token refresh attempt failed');
|
|
23
|
-
}
|
|
24
|
-
}, 4 * 60 * 1000); // 4 minutes
|
|
25
|
-
|
|
26
|
-
return () => {
|
|
27
|
-
if (intervalRef.current) {
|
|
28
|
-
clearInterval(intervalRef.current);
|
|
29
|
-
}
|
|
30
|
-
};
|
|
31
|
-
}, [get]);
|
|
32
|
-
|
|
33
|
-
return {
|
|
34
|
-
refreshToken: async () => {
|
|
35
|
-
try {
|
|
36
|
-
await get('/admin/users/me');
|
|
37
|
-
return true;
|
|
38
|
-
} catch (error) {
|
|
39
|
-
return false;
|
|
40
|
-
}
|
|
41
|
-
},
|
|
42
|
-
};
|
|
43
|
-
};
|
|
44
|
-
|
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect } from 'react';
|
|
2
|
-
import { useFetchClient } from '@strapi/strapi/admin';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Hook to check license status for MagicMail
|
|
6
|
-
* Returns: { isPremium, loading, error, licenseData, refetch }
|
|
7
|
-
*/
|
|
8
|
-
export const useLicense = () => {
|
|
9
|
-
const { get } = useFetchClient();
|
|
10
|
-
const [isPremium, setIsPremium] = useState(false);
|
|
11
|
-
const [isAdvanced, setIsAdvanced] = useState(false);
|
|
12
|
-
const [isEnterprise, setIsEnterprise] = useState(false);
|
|
13
|
-
const [loading, setLoading] = useState(true);
|
|
14
|
-
const [error, setError] = useState(null);
|
|
15
|
-
const [licenseData, setLicenseData] = useState(null);
|
|
16
|
-
|
|
17
|
-
useEffect(() => {
|
|
18
|
-
let mounted = true;
|
|
19
|
-
|
|
20
|
-
const fetchLicense = async () => {
|
|
21
|
-
if (mounted) {
|
|
22
|
-
await checkLicense();
|
|
23
|
-
}
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
fetchLicense();
|
|
27
|
-
|
|
28
|
-
// Auto-refresh every 1 hour
|
|
29
|
-
const interval = setInterval(() => {
|
|
30
|
-
if (mounted) {
|
|
31
|
-
checkLicense(true); // Silent refresh
|
|
32
|
-
}
|
|
33
|
-
}, 60 * 60 * 1000);
|
|
34
|
-
|
|
35
|
-
return () => {
|
|
36
|
-
mounted = false;
|
|
37
|
-
clearInterval(interval);
|
|
38
|
-
};
|
|
39
|
-
}, []);
|
|
40
|
-
|
|
41
|
-
const checkLicense = async (silent = false) => {
|
|
42
|
-
if (!silent) {
|
|
43
|
-
setLoading(true);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
try {
|
|
47
|
-
const response = await get('/magic-mail/license/status');
|
|
48
|
-
|
|
49
|
-
const isValid = response.data?.valid || false;
|
|
50
|
-
const hasPremiumFeature = response.data?.data?.features?.premium || false;
|
|
51
|
-
const hasAdvancedFeature = response.data?.data?.features?.advanced || false;
|
|
52
|
-
const hasEnterpriseFeature = response.data?.data?.features?.enterprise || false;
|
|
53
|
-
|
|
54
|
-
setIsPremium(isValid && hasPremiumFeature);
|
|
55
|
-
setIsAdvanced(isValid && hasAdvancedFeature);
|
|
56
|
-
setIsEnterprise(isValid && hasEnterpriseFeature);
|
|
57
|
-
setLicenseData(response.data?.data || null);
|
|
58
|
-
setError(null);
|
|
59
|
-
} catch (err) {
|
|
60
|
-
// Ignore AbortError (happens on unmount)
|
|
61
|
-
if (err.name === 'AbortError') {
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if (!silent) {
|
|
66
|
-
console.error('[MagicMail] License check error:', err);
|
|
67
|
-
}
|
|
68
|
-
setIsPremium(false);
|
|
69
|
-
setIsAdvanced(false);
|
|
70
|
-
setIsEnterprise(false);
|
|
71
|
-
setLicenseData(null);
|
|
72
|
-
setError(err);
|
|
73
|
-
} finally {
|
|
74
|
-
if (!silent) {
|
|
75
|
-
setLoading(false);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Check if a specific feature is available
|
|
82
|
-
* @param {string} featureName - Name of the feature to check
|
|
83
|
-
* @returns {boolean}
|
|
84
|
-
*/
|
|
85
|
-
const hasFeature = (featureName) => {
|
|
86
|
-
if (!featureName) return false;
|
|
87
|
-
|
|
88
|
-
// Free tier features (always available)
|
|
89
|
-
const freeFeatures = [
|
|
90
|
-
'basic-smtp',
|
|
91
|
-
'oauth-gmail',
|
|
92
|
-
'oauth-microsoft',
|
|
93
|
-
'oauth-yahoo',
|
|
94
|
-
'basic-routing',
|
|
95
|
-
'email-logging',
|
|
96
|
-
'account-testing',
|
|
97
|
-
'strapi-service-override',
|
|
98
|
-
'email-designer-basic',
|
|
99
|
-
];
|
|
100
|
-
|
|
101
|
-
if (freeFeatures.includes(featureName)) return true;
|
|
102
|
-
|
|
103
|
-
// Premium+ features
|
|
104
|
-
const premiumFeatures = [
|
|
105
|
-
'email-designer-templates',
|
|
106
|
-
];
|
|
107
|
-
|
|
108
|
-
if (premiumFeatures.includes(featureName) && isPremium) return true;
|
|
109
|
-
|
|
110
|
-
// Advanced+ features
|
|
111
|
-
const advancedFeatures = [
|
|
112
|
-
'sendgrid',
|
|
113
|
-
'mailgun',
|
|
114
|
-
'dkim-signing',
|
|
115
|
-
'priority-headers',
|
|
116
|
-
'list-unsubscribe',
|
|
117
|
-
'security-validation',
|
|
118
|
-
'analytics-dashboard',
|
|
119
|
-
'advanced-routing',
|
|
120
|
-
'email-designer-versioning',
|
|
121
|
-
'email-designer-import-export',
|
|
122
|
-
];
|
|
123
|
-
|
|
124
|
-
if (advancedFeatures.includes(featureName) && isAdvanced) return true;
|
|
125
|
-
|
|
126
|
-
// Enterprise features
|
|
127
|
-
const enterpriseFeatures = [
|
|
128
|
-
'multi-tenant',
|
|
129
|
-
'compliance-reports',
|
|
130
|
-
'custom-security-rules',
|
|
131
|
-
'priority-support',
|
|
132
|
-
'email-designer-custom-blocks',
|
|
133
|
-
'email-designer-team-library',
|
|
134
|
-
'email-designer-a-b-testing',
|
|
135
|
-
];
|
|
136
|
-
|
|
137
|
-
if (enterpriseFeatures.includes(featureName) && isEnterprise) return true;
|
|
138
|
-
|
|
139
|
-
return false;
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
return {
|
|
143
|
-
isPremium,
|
|
144
|
-
isAdvanced,
|
|
145
|
-
isEnterprise,
|
|
146
|
-
loading,
|
|
147
|
-
error,
|
|
148
|
-
licenseData,
|
|
149
|
-
features: {
|
|
150
|
-
premium: isPremium,
|
|
151
|
-
advanced: isAdvanced,
|
|
152
|
-
enterprise: isEnterprise,
|
|
153
|
-
},
|
|
154
|
-
hasFeature,
|
|
155
|
-
refetch: checkLicense
|
|
156
|
-
};
|
|
157
|
-
};
|
|
158
|
-
|