strapi-plugin-magic-mail 2.2.4 → 2.2.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 (71) hide show
  1. package/README.md +0 -2
  2. package/dist/server/index.js +1 -1
  3. package/dist/server/index.mjs +1 -1
  4. package/package.json +1 -3
  5. package/admin/jsconfig.json +0 -10
  6. package/admin/src/components/AddAccountModal.jsx +0 -1943
  7. package/admin/src/components/Initializer.jsx +0 -14
  8. package/admin/src/components/LicenseGuard.jsx +0 -475
  9. package/admin/src/components/PluginIcon.jsx +0 -5
  10. package/admin/src/hooks/useAuthRefresh.js +0 -44
  11. package/admin/src/hooks/useLicense.js +0 -158
  12. package/admin/src/index.js +0 -87
  13. package/admin/src/pages/Analytics.jsx +0 -762
  14. package/admin/src/pages/App.jsx +0 -111
  15. package/admin/src/pages/EmailDesigner/EditorPage.jsx +0 -1424
  16. package/admin/src/pages/EmailDesigner/TemplateList.jsx +0 -1807
  17. package/admin/src/pages/HomePage.jsx +0 -1170
  18. package/admin/src/pages/LicensePage.jsx +0 -430
  19. package/admin/src/pages/RoutingRules.jsx +0 -1141
  20. package/admin/src/pages/Settings.jsx +0 -603
  21. package/admin/src/pluginId.js +0 -3
  22. package/admin/src/translations/de.json +0 -71
  23. package/admin/src/translations/en.json +0 -70
  24. package/admin/src/translations/es.json +0 -71
  25. package/admin/src/translations/fr.json +0 -71
  26. package/admin/src/translations/pt.json +0 -71
  27. package/admin/src/utils/fetchWithRetry.js +0 -123
  28. package/admin/src/utils/getTranslation.js +0 -5
  29. package/admin/src/utils/theme.js +0 -85
  30. package/server/jsconfig.json +0 -10
  31. package/server/src/bootstrap.js +0 -157
  32. package/server/src/config/features.js +0 -260
  33. package/server/src/config/index.js +0 -9
  34. package/server/src/content-types/email-account/schema.json +0 -93
  35. package/server/src/content-types/email-event/index.js +0 -8
  36. package/server/src/content-types/email-event/schema.json +0 -57
  37. package/server/src/content-types/email-link/index.js +0 -8
  38. package/server/src/content-types/email-link/schema.json +0 -49
  39. package/server/src/content-types/email-log/index.js +0 -8
  40. package/server/src/content-types/email-log/schema.json +0 -106
  41. package/server/src/content-types/email-template/schema.json +0 -74
  42. package/server/src/content-types/email-template-version/schema.json +0 -60
  43. package/server/src/content-types/index.js +0 -33
  44. package/server/src/content-types/routing-rule/schema.json +0 -59
  45. package/server/src/controllers/accounts.js +0 -229
  46. package/server/src/controllers/analytics.js +0 -361
  47. package/server/src/controllers/controller.js +0 -26
  48. package/server/src/controllers/email-designer.js +0 -474
  49. package/server/src/controllers/index.js +0 -21
  50. package/server/src/controllers/license.js +0 -269
  51. package/server/src/controllers/oauth.js +0 -474
  52. package/server/src/controllers/routing-rules.js +0 -129
  53. package/server/src/controllers/test.js +0 -301
  54. package/server/src/destroy.js +0 -27
  55. package/server/src/index.js +0 -25
  56. package/server/src/middlewares/index.js +0 -3
  57. package/server/src/policies/index.js +0 -3
  58. package/server/src/register.js +0 -5
  59. package/server/src/routes/admin.js +0 -469
  60. package/server/src/routes/content-api.js +0 -37
  61. package/server/src/routes/index.js +0 -9
  62. package/server/src/services/account-manager.js +0 -329
  63. package/server/src/services/analytics.js +0 -512
  64. package/server/src/services/email-designer.js +0 -717
  65. package/server/src/services/email-router.js +0 -1446
  66. package/server/src/services/index.js +0 -17
  67. package/server/src/services/license-guard.js +0 -423
  68. package/server/src/services/oauth.js +0 -515
  69. package/server/src/services/service.js +0 -7
  70. package/server/src/utils/encryption.js +0 -81
  71. 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,5 +0,0 @@
1
- import { EnvelopeIcon } from '@heroicons/react/24/outline';
2
-
3
- const PluginIcon = () => <EnvelopeIcon style={{ width: 24, height: 24 }} />;
4
-
5
- export default PluginIcon;
@@ -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
-