strapi-plugin-magic-sessionmanager 4.0.0 → 4.0.2
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/README.md +2 -2
- package/admin/src/components/LicenseGuard.jsx +6 -6
- package/admin/src/components/OnlineUsersWidget.jsx +11 -7
- package/admin/src/components/SessionDetailModal.jsx +45 -41
- package/admin/src/components/SessionInfoCard.jsx +3 -3
- package/admin/src/components/SessionInfoPanel.jsx +31 -21
- package/admin/src/index.js +9 -0
- package/admin/src/pages/Analytics.jsx +2 -2
- package/admin/src/pages/HomePage.jsx +129 -165
- package/admin/src/pages/License.jsx +5 -5
- package/admin/src/pages/Settings.jsx +148 -144
- package/admin/src/pages/SettingsNew.jsx +21 -21
- package/admin/src/pages/UpgradePage.jsx +448 -0
- package/admin/src/pluginId.js +1 -0
- package/admin/src/translations/de.json +294 -15
- package/admin/src/translations/en.json +293 -14
- package/admin/src/translations/es.json +284 -18
- package/admin/src/translations/fr.json +284 -18
- package/admin/src/translations/pt.json +284 -18
- package/admin/src/utils/parseUserAgent.js +6 -6
- package/admin/src/utils/theme.js +85 -0
- package/dist/_chunks/{Analytics-mYu_uGwU.mjs → Analytics-DTE_zmRV.mjs} +4 -4
- package/dist/_chunks/{Analytics-ioaeEh-E.js → Analytics-lw_JaOVy.js} +4 -4
- package/dist/_chunks/{App-DdnUYWbC.js → App-DDKYCjKw.js} +221 -216
- package/dist/_chunks/{App-BXpIS12l.mjs → App-DJW1ZNl5.mjs} +221 -216
- package/dist/_chunks/{License-C03C2j9P.mjs → License-DaOFuImm.mjs} +6 -10
- package/dist/_chunks/{License-DZYrOgcx.js → License-Tk-6UfPl.js} +6 -10
- package/dist/_chunks/{OnlineUsersWidget-B8JS1xZu.js → OnlineUsersWidget-C1qTpsws.js} +11 -7
- package/dist/_chunks/{OnlineUsersWidget-ArMl0nen.mjs → OnlineUsersWidget-CADphbXG.mjs} +11 -7
- package/dist/_chunks/{Settings-0ocB3qHk.mjs → Settings-C9xvckgq.mjs} +200 -188
- package/dist/_chunks/{Settings-C6_CqpCC.js → Settings-DyEAuTNQ.js} +200 -188
- package/dist/_chunks/UpgradePage-Dssk8A0Z.js +354 -0
- package/dist/_chunks/UpgradePage-cINvE9zY.mjs +352 -0
- package/dist/_chunks/de-CDA1V0rF.mjs +292 -0
- package/dist/_chunks/de-I-Q-pWqu.js +292 -0
- package/dist/_chunks/en-Bd7_h-4e.js +292 -0
- package/dist/_chunks/en-DzmOCyzQ.mjs +292 -0
- package/dist/_chunks/es-BcAx18XG.js +277 -0
- package/dist/_chunks/es-Cx-SN6qV.mjs +277 -0
- package/dist/_chunks/fr-DCzYMuJ-.js +277 -0
- package/dist/_chunks/fr-DXlXE5Eo.mjs +277 -0
- package/dist/_chunks/{index-DC8Y0qxx.js → index-CWcvrfXc.js} +52 -49
- package/dist/_chunks/{index-DBRS3kt5.mjs → index-DQO9bNP7.mjs} +52 -49
- package/dist/_chunks/pt-21-MAb72.js +277 -0
- package/dist/_chunks/pt-zsdTSjba.mjs +277 -0
- package/dist/_chunks/{useLicense-qgGfMvse.js → useLicense-DtvJOszr.js} +1 -1
- package/dist/_chunks/{useLicense-DSLL9n3Y.mjs → useLicense-DxbD4Wf8.mjs} +1 -1
- package/dist/admin/index.js +1 -1
- package/dist/admin/index.mjs +1 -1
- package/dist/server/index.js +142 -33
- package/dist/server/index.mjs +142 -33
- package/package.json +1 -1
- package/server/src/bootstrap.js +76 -4
- package/server/src/controllers/session.js +59 -9
- package/server/src/middlewares/last-seen.js +5 -4
- package/server/src/routes/content-api.js +11 -2
- package/server/src/services/notifications.js +10 -10
- package/server/src/services/session.js +24 -4
- package/dist/_chunks/de-BxFx1pwE.js +0 -23
- package/dist/_chunks/de-CdO3s01z.mjs +0 -23
- package/dist/_chunks/en-CsPpPJL3.mjs +0 -23
- package/dist/_chunks/en-RqmpDHdS.js +0 -23
- package/dist/_chunks/es-CuLHazN1.js +0 -23
- package/dist/_chunks/es-Dkmjhy9c.mjs +0 -23
- package/dist/_chunks/fr-BAJp2yhI.js +0 -23
- package/dist/_chunks/fr-Bssg_3UF.mjs +0 -23
- package/dist/_chunks/pt-BAP9cKs3.js +0 -23
- package/dist/_chunks/pt-BVNoNcuY.mjs +0 -23
|
@@ -206,12 +206,12 @@ const validateTemplate = (template, templateType) => {
|
|
|
206
206
|
// Get default email templates
|
|
207
207
|
const getDefaultTemplates = () => ({
|
|
208
208
|
suspiciousLogin: {
|
|
209
|
-
subject: '
|
|
209
|
+
subject: '[ALERT] Suspicious Login Alert - Session Manager',
|
|
210
210
|
html: `
|
|
211
211
|
<html>
|
|
212
212
|
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
|
|
213
213
|
<div style="max-width: 600px; margin: 0 auto; padding: 20px; background-color: #f9fafb; border-radius: 10px;">
|
|
214
|
-
<h2 style="color: #dc2626;"
|
|
214
|
+
<h2 style="color: #dc2626;">[ALERT] Suspicious Login Detected</h2>
|
|
215
215
|
<p>A potentially suspicious login was detected for your account.</p>
|
|
216
216
|
|
|
217
217
|
<div style="background: white; padding: 15px; border-radius: 8px; margin: 20px 0;">
|
|
@@ -246,7 +246,7 @@ const getDefaultTemplates = () => ({
|
|
|
246
246
|
</div>
|
|
247
247
|
</body>
|
|
248
248
|
</html>`,
|
|
249
|
-
text:
|
|
249
|
+
text: `[ALERT] Suspicious Login Detected
|
|
250
250
|
|
|
251
251
|
A potentially suspicious login was detected for your account.
|
|
252
252
|
|
|
@@ -261,12 +261,12 @@ Login Details:
|
|
|
261
261
|
Security: VPN={{reason.isVpn}}, Proxy={{reason.isProxy}}, Threat={{reason.isThreat}}, Score={{reason.securityScore}}/100`,
|
|
262
262
|
},
|
|
263
263
|
newLocation: {
|
|
264
|
-
subject: '
|
|
264
|
+
subject: '[LOCATION] New Location Login Detected',
|
|
265
265
|
html: `
|
|
266
266
|
<html>
|
|
267
267
|
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
|
|
268
268
|
<div style="max-width: 600px; margin: 0 auto; padding: 20px; background-color: #f0f9ff; border-radius: 10px;">
|
|
269
|
-
<h2 style="color: #0284c7;"
|
|
269
|
+
<h2 style="color: #0284c7;">[LOCATION] Login from New Location</h2>
|
|
270
270
|
<p>Your account was accessed from a new location.</p>
|
|
271
271
|
|
|
272
272
|
<div style="background: white; padding: 15px; border-radius: 8px; margin: 20px 0;">
|
|
@@ -289,7 +289,7 @@ Security: VPN={{reason.isVpn}}, Proxy={{reason.isProxy}}, Threat={{reason.isThre
|
|
|
289
289
|
</div>
|
|
290
290
|
</body>
|
|
291
291
|
</html>`,
|
|
292
|
-
text:
|
|
292
|
+
text: `[LOCATION] Login from New Location
|
|
293
293
|
|
|
294
294
|
Your account was accessed from a new location.
|
|
295
295
|
|
|
@@ -304,12 +304,12 @@ New Location Details:
|
|
|
304
304
|
If this was you, no action is needed.`,
|
|
305
305
|
},
|
|
306
306
|
vpnProxy: {
|
|
307
|
-
subject: '
|
|
307
|
+
subject: '[WARNING] VPN/Proxy Login Detected',
|
|
308
308
|
html: `
|
|
309
309
|
<html>
|
|
310
310
|
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
|
|
311
311
|
<div style="max-width: 600px; margin: 0 auto; padding: 20px; background-color: #fffbeb; border-radius: 10px;">
|
|
312
|
-
<h2 style="color: #d97706;"
|
|
312
|
+
<h2 style="color: #d97706;">[WARNING] VPN/Proxy Detected</h2>
|
|
313
313
|
<p>A login from a VPN or proxy service was detected on your account.</p>
|
|
314
314
|
|
|
315
315
|
<div style="background: white; padding: 15px; border-radius: 8px; margin: 20px 0;">
|
|
@@ -334,7 +334,7 @@ If this was you, no action is needed.`,
|
|
|
334
334
|
</div>
|
|
335
335
|
</body>
|
|
336
336
|
</html>`,
|
|
337
|
-
text:
|
|
337
|
+
text: `[WARNING] VPN/Proxy Detected
|
|
338
338
|
|
|
339
339
|
A login from a VPN or proxy service was detected on your account.
|
|
340
340
|
|
|
@@ -444,7 +444,7 @@ const SettingsPage = () => {
|
|
|
444
444
|
};
|
|
445
445
|
|
|
446
446
|
const handleCleanInactive = async () => {
|
|
447
|
-
if (!confirm('
|
|
447
|
+
if (!confirm('[WARNING] This will permanently delete ALL inactive sessions.\n\nContinue?')) {
|
|
448
448
|
return;
|
|
449
449
|
}
|
|
450
450
|
|
|
@@ -553,7 +553,7 @@ const SettingsPage = () => {
|
|
|
553
553
|
|
|
554
554
|
{/* Session Timeout */}
|
|
555
555
|
<Typography variant="sigma" fontWeight="bold" style={{ marginBottom: '16px', display: 'block', color: theme.colors.neutral[700] }}>
|
|
556
|
-
|
|
556
|
+
SESSION TIMEOUT
|
|
557
557
|
</Typography>
|
|
558
558
|
<Grid.Root gap={6} style={{ marginBottom: '32px' }}>
|
|
559
559
|
<Grid.Item col={6} s={12}>
|
|
@@ -694,7 +694,7 @@ const SettingsPage = () => {
|
|
|
694
694
|
<Box padding={6}>
|
|
695
695
|
|
|
696
696
|
<Typography variant="sigma" fontWeight="bold" style={{ marginBottom: '16px', display: 'block', color: theme.colors.neutral[700] }}>
|
|
697
|
-
|
|
697
|
+
SECURITY OPTIONS
|
|
698
698
|
</Typography>
|
|
699
699
|
|
|
700
700
|
{/* Feature Toggles */}
|
|
@@ -866,7 +866,7 @@ const SettingsPage = () => {
|
|
|
866
866
|
checked={settings.alertOnSuspiciousLogin}
|
|
867
867
|
onChange={() => handleChange('alertOnSuspiciousLogin', !settings.alertOnSuspiciousLogin)}
|
|
868
868
|
>
|
|
869
|
-
|
|
869
|
+
Suspicious Login
|
|
870
870
|
</Checkbox>
|
|
871
871
|
</Box>
|
|
872
872
|
</Grid.Item>
|
|
@@ -876,7 +876,7 @@ const SettingsPage = () => {
|
|
|
876
876
|
checked={settings.alertOnNewLocation}
|
|
877
877
|
onChange={() => handleChange('alertOnNewLocation', !settings.alertOnNewLocation)}
|
|
878
878
|
>
|
|
879
|
-
|
|
879
|
+
New Location
|
|
880
880
|
</Checkbox>
|
|
881
881
|
</Box>
|
|
882
882
|
</Grid.Item>
|
|
@@ -886,7 +886,7 @@ const SettingsPage = () => {
|
|
|
886
886
|
checked={settings.alertOnVpnProxy}
|
|
887
887
|
onChange={() => handleChange('alertOnVpnProxy', !settings.alertOnVpnProxy)}
|
|
888
888
|
>
|
|
889
|
-
|
|
889
|
+
VPN/Proxy
|
|
890
890
|
</Checkbox>
|
|
891
891
|
</Box>
|
|
892
892
|
</Grid.Item>
|
|
@@ -895,7 +895,7 @@ const SettingsPage = () => {
|
|
|
895
895
|
{/* Email Templates */}
|
|
896
896
|
<Divider style={{ marginBottom: '24px' }} />
|
|
897
897
|
<Typography variant="sigma" fontWeight="bold" style={{ marginBottom: '8px', display: 'block', color: theme.colors.neutral[700] }}>
|
|
898
|
-
|
|
898
|
+
EMAIL TEMPLATES
|
|
899
899
|
</Typography>
|
|
900
900
|
<Typography variant="pi" textColor="neutral600" style={{ marginBottom: '20px', display: 'block', fontSize: '12px' }}>
|
|
901
901
|
Customize email notification templates with dynamic variables
|
|
@@ -904,9 +904,9 @@ const SettingsPage = () => {
|
|
|
904
904
|
{/* Template Tabs */}
|
|
905
905
|
<Tabs.Root value={activeTemplateTab} onValueChange={setActiveTemplateTab}>
|
|
906
906
|
<Tabs.List aria-label="Email Templates">
|
|
907
|
-
<Tabs.Trigger value="suspiciousLogin"
|
|
908
|
-
<Tabs.Trigger value="newLocation"
|
|
909
|
-
<Tabs.Trigger value="vpnProxy"
|
|
907
|
+
<Tabs.Trigger value="suspiciousLogin">Suspicious Login</Tabs.Trigger>
|
|
908
|
+
<Tabs.Trigger value="newLocation">New Location</Tabs.Trigger>
|
|
909
|
+
<Tabs.Trigger value="vpnProxy">VPN/Proxy</Tabs.Trigger>
|
|
910
910
|
</Tabs.List>
|
|
911
911
|
|
|
912
912
|
{Object.keys(settings.emailTemplates).map((templateKey) => (
|
|
@@ -915,7 +915,7 @@ const SettingsPage = () => {
|
|
|
915
915
|
{/* Subject */}
|
|
916
916
|
<Box style={{ marginBottom: '24px' }}>
|
|
917
917
|
<Typography variant="pi" fontWeight="bold" style={{ marginBottom: '8px', display: 'block' }}>
|
|
918
|
-
|
|
918
|
+
Email Subject
|
|
919
919
|
</Typography>
|
|
920
920
|
<TextInput
|
|
921
921
|
value={settings.emailTemplates[templateKey].subject}
|
|
@@ -1151,7 +1151,7 @@ const SettingsPage = () => {
|
|
|
1151
1151
|
<Information style={{ width: '20px', height: '20px', color: theme.colors.neutral[600], flexShrink: 0, marginTop: '2px' }} />
|
|
1152
1152
|
<Box style={{ flex: 1 }}>
|
|
1153
1153
|
<Typography variant="omega" fontWeight="bold" style={{ marginBottom: '8px', display: 'block' }}>
|
|
1154
|
-
|
|
1154
|
+
How to Apply These Settings
|
|
1155
1155
|
</Typography>
|
|
1156
1156
|
<Typography variant="pi" textColor="neutral600" style={{ fontSize: '13px', lineHeight: '1.8' }}>
|
|
1157
1157
|
Settings are saved in your browser. To apply permanently, copy the config below and paste it into{' '}
|
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
import { useFetchClient, useNotification } from '@strapi/strapi/admin';
|
|
3
|
+
import styled from 'styled-components';
|
|
4
|
+
import {
|
|
5
|
+
Box,
|
|
6
|
+
Button,
|
|
7
|
+
Flex,
|
|
8
|
+
Typography,
|
|
9
|
+
Badge,
|
|
10
|
+
} from '@strapi/design-system';
|
|
11
|
+
import {
|
|
12
|
+
Check as CheckIcon,
|
|
13
|
+
Cross as XMarkIcon,
|
|
14
|
+
Sparkle as SparklesIcon,
|
|
15
|
+
Lightning as BoltIcon,
|
|
16
|
+
Rocket as RocketLaunchIcon,
|
|
17
|
+
} from '@strapi/icons';
|
|
18
|
+
import pluginId from '../pluginId';
|
|
19
|
+
|
|
20
|
+
const Container = styled(Box)`
|
|
21
|
+
padding: 32px;
|
|
22
|
+
max-width: 1400px;
|
|
23
|
+
margin: 0 auto;
|
|
24
|
+
`;
|
|
25
|
+
|
|
26
|
+
const Header = styled(Box)`
|
|
27
|
+
text-align: center;
|
|
28
|
+
margin-bottom: 48px;
|
|
29
|
+
display: flex;
|
|
30
|
+
flex-direction: column;
|
|
31
|
+
align-items: center;
|
|
32
|
+
gap: 8px;
|
|
33
|
+
`;
|
|
34
|
+
|
|
35
|
+
const Title = styled(Typography)`
|
|
36
|
+
font-size: 2.5rem;
|
|
37
|
+
font-weight: 700;
|
|
38
|
+
margin-bottom: 8px;
|
|
39
|
+
background: linear-gradient(135deg, #0EA5E9, #A855F7);
|
|
40
|
+
-webkit-background-clip: text;
|
|
41
|
+
-webkit-text-fill-color: transparent;
|
|
42
|
+
display: block;
|
|
43
|
+
`;
|
|
44
|
+
|
|
45
|
+
const Subtitle = styled(Typography)`
|
|
46
|
+
font-size: 1.125rem;
|
|
47
|
+
color: ${props => props.theme.colors.neutral600};
|
|
48
|
+
line-height: 1.6;
|
|
49
|
+
display: block;
|
|
50
|
+
`;
|
|
51
|
+
|
|
52
|
+
const TierGrid = styled(Flex)`
|
|
53
|
+
gap: 32px;
|
|
54
|
+
margin: 0 auto 48px;
|
|
55
|
+
max-width: 1080px;
|
|
56
|
+
justify-content: center;
|
|
57
|
+
flex-wrap: wrap;
|
|
58
|
+
align-items: stretch;
|
|
59
|
+
`;
|
|
60
|
+
|
|
61
|
+
const TierWrapper = styled(Box)`
|
|
62
|
+
flex: 1;
|
|
63
|
+
min-width: 280px;
|
|
64
|
+
max-width: 340px;
|
|
65
|
+
display: flex;
|
|
66
|
+
`;
|
|
67
|
+
|
|
68
|
+
const TierCard = styled(Box)`
|
|
69
|
+
background: ${props => props.theme.colors.neutral0};
|
|
70
|
+
border-radius: 16px;
|
|
71
|
+
padding: 32px;
|
|
72
|
+
border: 2px solid ${props => props.$featured ? '#0EA5E9' : props.theme.colors.neutral200};
|
|
73
|
+
position: relative;
|
|
74
|
+
transition: all 0.3s ease;
|
|
75
|
+
box-shadow: ${props => props.$featured
|
|
76
|
+
? '0 20px 25px -5px rgba(14, 165, 233, 0.25), 0 8px 10px -6px rgba(14, 165, 233, 0.2)'
|
|
77
|
+
: '0 10px 15px -3px rgba(15, 23, 42, 0.08), 0 4px 6px -4px rgba(15, 23, 42, 0.05)'};
|
|
78
|
+
display: flex;
|
|
79
|
+
flex-direction: column;
|
|
80
|
+
width: 100%;
|
|
81
|
+
|
|
82
|
+
&:hover {
|
|
83
|
+
transform: translateY(-4px);
|
|
84
|
+
box-shadow: 0 20px 25px -5px rgba(15, 23, 42, 0.15), 0 8px 10px -6px rgba(15, 23, 42, 0.1);
|
|
85
|
+
}
|
|
86
|
+
`;
|
|
87
|
+
|
|
88
|
+
const PopularBadge = styled(Badge)`
|
|
89
|
+
position: absolute;
|
|
90
|
+
top: -12px;
|
|
91
|
+
right: 24px;
|
|
92
|
+
background: linear-gradient(135deg, #0EA5E9, #0284C7);
|
|
93
|
+
color: white;
|
|
94
|
+
padding: 4px 16px;
|
|
95
|
+
font-size: 12px;
|
|
96
|
+
font-weight: 600;
|
|
97
|
+
`;
|
|
98
|
+
|
|
99
|
+
const TierIcon = styled(Box)`
|
|
100
|
+
width: 48px;
|
|
101
|
+
height: 48px;
|
|
102
|
+
border-radius: 12px;
|
|
103
|
+
display: flex;
|
|
104
|
+
align-items: center;
|
|
105
|
+
justify-content: center;
|
|
106
|
+
margin-bottom: 16px;
|
|
107
|
+
background: ${props => props.$color};
|
|
108
|
+
|
|
109
|
+
svg {
|
|
110
|
+
width: 28px;
|
|
111
|
+
height: 28px;
|
|
112
|
+
color: white;
|
|
113
|
+
}
|
|
114
|
+
`;
|
|
115
|
+
|
|
116
|
+
const TierName = styled(Typography)`
|
|
117
|
+
font-size: 1.5rem;
|
|
118
|
+
font-weight: 700;
|
|
119
|
+
margin-bottom: 8px;
|
|
120
|
+
`;
|
|
121
|
+
|
|
122
|
+
const TierPrice = styled(Typography)`
|
|
123
|
+
font-size: 2rem;
|
|
124
|
+
font-weight: 800;
|
|
125
|
+
margin-bottom: 4px;
|
|
126
|
+
`;
|
|
127
|
+
|
|
128
|
+
const TierDescription = styled(Typography)`
|
|
129
|
+
color: ${props => props.theme.colors.neutral600};
|
|
130
|
+
margin-bottom: 24px;
|
|
131
|
+
`;
|
|
132
|
+
|
|
133
|
+
const FeatureList = styled(Box)`
|
|
134
|
+
margin-bottom: 24px;
|
|
135
|
+
flex: 1;
|
|
136
|
+
`;
|
|
137
|
+
|
|
138
|
+
const Feature = styled(Flex)`
|
|
139
|
+
gap: 12px;
|
|
140
|
+
margin-bottom: 12px;
|
|
141
|
+
align-items: flex-start;
|
|
142
|
+
`;
|
|
143
|
+
|
|
144
|
+
const FeatureIcon = styled(Box)`
|
|
145
|
+
width: 20px;
|
|
146
|
+
height: 20px;
|
|
147
|
+
border-radius: 50%;
|
|
148
|
+
display: flex;
|
|
149
|
+
align-items: center;
|
|
150
|
+
justify-content: center;
|
|
151
|
+
flex-shrink: 0;
|
|
152
|
+
margin-top: 2px;
|
|
153
|
+
|
|
154
|
+
${props => props.$included ? `
|
|
155
|
+
background: #DCFCE7;
|
|
156
|
+
svg { color: #16A34A; }
|
|
157
|
+
` : `
|
|
158
|
+
background: #FEE2E2;
|
|
159
|
+
svg { color: #DC2626; }
|
|
160
|
+
`}
|
|
161
|
+
`;
|
|
162
|
+
|
|
163
|
+
const UpgradeButton = styled(Button)`
|
|
164
|
+
width: 100%;
|
|
165
|
+
height: 48px;
|
|
166
|
+
font-weight: 600;
|
|
167
|
+
font-size: 15px;
|
|
168
|
+
background: ${props => props.$gradient};
|
|
169
|
+
border: none;
|
|
170
|
+
color: white;
|
|
171
|
+
|
|
172
|
+
&:hover {
|
|
173
|
+
transform: translateY(-2px);
|
|
174
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
175
|
+
}
|
|
176
|
+
`;
|
|
177
|
+
|
|
178
|
+
const CurrentPlanBadge = styled(Badge)`
|
|
179
|
+
width: 100%;
|
|
180
|
+
height: 48px;
|
|
181
|
+
display: flex;
|
|
182
|
+
align-items: center;
|
|
183
|
+
justify-content: center;
|
|
184
|
+
background: ${props => props.theme.colors.neutral100};
|
|
185
|
+
color: ${props => props.theme.colors.neutral600};
|
|
186
|
+
font-weight: 600;
|
|
187
|
+
font-size: 15px;
|
|
188
|
+
`;
|
|
189
|
+
|
|
190
|
+
const LimitsBox = styled(Box)`
|
|
191
|
+
background: ${props => props.theme.colors.neutral100};
|
|
192
|
+
border-radius: 8px;
|
|
193
|
+
padding: 12px;
|
|
194
|
+
margin-bottom: 20px;
|
|
195
|
+
`;
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Upgrade page for Magic Sessionmanager with pricing tiers
|
|
199
|
+
*/
|
|
200
|
+
const UpgradePage = () => {
|
|
201
|
+
const { get } = useFetchClient();
|
|
202
|
+
const { toggleNotification } = useNotification();
|
|
203
|
+
const [currentTier, setCurrentTier] = useState('free');
|
|
204
|
+
const [limits, setLimits] = useState(null);
|
|
205
|
+
const [loading, setLoading] = useState(true);
|
|
206
|
+
|
|
207
|
+
useEffect(() => {
|
|
208
|
+
fetchLicenseInfo();
|
|
209
|
+
}, []);
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Fetches current license information
|
|
213
|
+
*/
|
|
214
|
+
const fetchLicenseInfo = async () => {
|
|
215
|
+
try {
|
|
216
|
+
const response = await get(`/${pluginId}/license/status`);
|
|
217
|
+
const licenseData = response.data || {};
|
|
218
|
+
|
|
219
|
+
let tier = 'free';
|
|
220
|
+
if (licenseData.data?.features?.advanced) {
|
|
221
|
+
tier = 'advanced';
|
|
222
|
+
} else if (licenseData.data?.features?.premium) {
|
|
223
|
+
tier = 'premium';
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
setCurrentTier(tier);
|
|
227
|
+
setLimits(licenseData.limits);
|
|
228
|
+
setLoading(false);
|
|
229
|
+
} catch (error) {
|
|
230
|
+
console.error('[magic-sessionmanager] Failed to fetch license info:', error);
|
|
231
|
+
setLoading(false);
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Returns rank of tier for comparison
|
|
237
|
+
*/
|
|
238
|
+
const getTierRank = (tierId) => {
|
|
239
|
+
const ranks = {
|
|
240
|
+
'free': 0,
|
|
241
|
+
'premium': 1,
|
|
242
|
+
'advanced': 2,
|
|
243
|
+
};
|
|
244
|
+
return ranks[tierId] || 0;
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Returns button text based on tier comparison
|
|
249
|
+
*/
|
|
250
|
+
const getButtonText = (tierId) => {
|
|
251
|
+
const currentRank = getTierRank(currentTier);
|
|
252
|
+
const targetRank = getTierRank(tierId);
|
|
253
|
+
|
|
254
|
+
if (currentRank === targetRank) {
|
|
255
|
+
return 'Current Plan';
|
|
256
|
+
} else if (targetRank > currentRank) {
|
|
257
|
+
return 'Upgrade Now';
|
|
258
|
+
} else {
|
|
259
|
+
return 'Downgrade';
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
const tiers = [
|
|
264
|
+
{
|
|
265
|
+
id: 'free',
|
|
266
|
+
name: 'FREE',
|
|
267
|
+
price: '$0',
|
|
268
|
+
period: 'forever',
|
|
269
|
+
description: 'Perfect for small projects and testing',
|
|
270
|
+
icon: <SparklesIcon />,
|
|
271
|
+
color: 'linear-gradient(135deg, #6B7280, #4B5563)',
|
|
272
|
+
features: [
|
|
273
|
+
{ name: 'Session Tracking', included: true },
|
|
274
|
+
{ name: 'Force Logout', included: true },
|
|
275
|
+
{ name: 'Dashboard Widget', included: true },
|
|
276
|
+
{ name: 'Device Detection', included: true },
|
|
277
|
+
{ name: 'Basic Monitoring', included: true },
|
|
278
|
+
{ name: 'IP Geolocation', included: false },
|
|
279
|
+
{ name: 'VPN/Proxy Detection', included: false },
|
|
280
|
+
{ name: 'Threat Analysis', included: false },
|
|
281
|
+
{ name: 'Auto-blocking', included: false },
|
|
282
|
+
],
|
|
283
|
+
limits: {
|
|
284
|
+
sessions: 'Unlimited',
|
|
285
|
+
support: 'Community',
|
|
286
|
+
}
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
id: 'premium',
|
|
290
|
+
name: 'PREMIUM',
|
|
291
|
+
price: '$14.50',
|
|
292
|
+
period: '/month',
|
|
293
|
+
description: 'Enhanced security for growing teams',
|
|
294
|
+
icon: <BoltIcon />,
|
|
295
|
+
color: 'linear-gradient(135deg, #8B5CF6, #7C3AED)',
|
|
296
|
+
featured: true,
|
|
297
|
+
features: [
|
|
298
|
+
{ name: 'Session Tracking', included: true },
|
|
299
|
+
{ name: 'Force Logout', included: true },
|
|
300
|
+
{ name: 'Dashboard Widget', included: true },
|
|
301
|
+
{ name: 'Device Detection', included: true },
|
|
302
|
+
{ name: 'Basic Monitoring', included: true },
|
|
303
|
+
{ name: 'IP Geolocation', included: true },
|
|
304
|
+
{ name: 'VPN/Proxy Detection', included: true },
|
|
305
|
+
{ name: 'Security Risk Scoring', included: true },
|
|
306
|
+
{ name: 'Extended Logging', included: true },
|
|
307
|
+
{ name: 'Priority Support', included: true },
|
|
308
|
+
],
|
|
309
|
+
limits: {
|
|
310
|
+
sessions: 'Unlimited',
|
|
311
|
+
support: 'Priority',
|
|
312
|
+
}
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
id: 'advanced',
|
|
316
|
+
name: 'ADVANCED',
|
|
317
|
+
price: '$39.50',
|
|
318
|
+
period: '/month',
|
|
319
|
+
description: 'Maximum security for enterprises',
|
|
320
|
+
icon: <RocketLaunchIcon />,
|
|
321
|
+
color: 'linear-gradient(135deg, #0EA5E9, #0284C7)',
|
|
322
|
+
features: [
|
|
323
|
+
{ name: 'Session Tracking', included: true },
|
|
324
|
+
{ name: 'Force Logout', included: true },
|
|
325
|
+
{ name: 'Dashboard Widget', included: true },
|
|
326
|
+
{ name: 'Device Detection', included: true },
|
|
327
|
+
{ name: 'Basic Monitoring', included: true },
|
|
328
|
+
{ name: 'IP Geolocation', included: true },
|
|
329
|
+
{ name: 'VPN/Proxy Detection', included: true },
|
|
330
|
+
{ name: 'Security Risk Scoring', included: true },
|
|
331
|
+
{ name: 'Extended Logging', included: true },
|
|
332
|
+
{ name: 'Threat Analysis', included: true },
|
|
333
|
+
{ name: 'Auto-blocking', included: true },
|
|
334
|
+
{ name: 'Email/Webhook Alerts', included: true },
|
|
335
|
+
{ name: 'Geo-fencing', included: true },
|
|
336
|
+
{ name: 'Priority + Phone Support', included: true },
|
|
337
|
+
],
|
|
338
|
+
limits: {
|
|
339
|
+
sessions: 'Unlimited',
|
|
340
|
+
support: 'Priority + Phone',
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
];
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Handle upgrade button click
|
|
347
|
+
*/
|
|
348
|
+
const handleUpgrade = (tierId) => {
|
|
349
|
+
window.open('https://store.magicdx.dev/', '_blank');
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
if (loading) {
|
|
353
|
+
return (
|
|
354
|
+
<Container>
|
|
355
|
+
<Flex justifyContent="center" alignItems="center" style={{ minHeight: '400px' }}>
|
|
356
|
+
<Typography>Loading license information...</Typography>
|
|
357
|
+
</Flex>
|
|
358
|
+
</Container>
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return (
|
|
363
|
+
<Container>
|
|
364
|
+
<Header>
|
|
365
|
+
<Title variant="alpha">Choose Your Plan</Title>
|
|
366
|
+
<Subtitle variant="omega">
|
|
367
|
+
Unlock powerful session management features for your Strapi application
|
|
368
|
+
</Subtitle>
|
|
369
|
+
</Header>
|
|
370
|
+
|
|
371
|
+
<TierGrid>
|
|
372
|
+
{tiers.map((tier) => (
|
|
373
|
+
<TierWrapper key={tier.id}>
|
|
374
|
+
<TierCard $featured={tier.featured}>
|
|
375
|
+
{tier.featured && <PopularBadge>MOST POPULAR</PopularBadge>}
|
|
376
|
+
|
|
377
|
+
<TierIcon $color={tier.color}>
|
|
378
|
+
{tier.icon}
|
|
379
|
+
</TierIcon>
|
|
380
|
+
|
|
381
|
+
<TierName variant="beta">{tier.name}</TierName>
|
|
382
|
+
|
|
383
|
+
<Flex alignItems="baseline" gap={1}>
|
|
384
|
+
<TierPrice variant="alpha">{tier.price}</TierPrice>
|
|
385
|
+
<Typography variant="omega" style={{ color: '#6B7280' }}>
|
|
386
|
+
{tier.period}
|
|
387
|
+
</Typography>
|
|
388
|
+
</Flex>
|
|
389
|
+
|
|
390
|
+
<TierDescription variant="omega">
|
|
391
|
+
{tier.description}
|
|
392
|
+
</TierDescription>
|
|
393
|
+
|
|
394
|
+
<LimitsBox>
|
|
395
|
+
<Flex direction="column" gap={2}>
|
|
396
|
+
<Typography variant="pi" style={{ fontSize: '13px' }}>
|
|
397
|
+
<strong>Sessions:</strong> {tier.limits.sessions}
|
|
398
|
+
</Typography>
|
|
399
|
+
<Typography variant="pi" style={{ fontSize: '13px' }}>
|
|
400
|
+
<strong>Support:</strong> {tier.limits.support}
|
|
401
|
+
</Typography>
|
|
402
|
+
</Flex>
|
|
403
|
+
</LimitsBox>
|
|
404
|
+
|
|
405
|
+
<FeatureList>
|
|
406
|
+
{tier.features.map((feature, index) => (
|
|
407
|
+
<Feature key={index}>
|
|
408
|
+
<FeatureIcon $included={feature.included}>
|
|
409
|
+
{feature.included ? (
|
|
410
|
+
<CheckIcon style={{ width: 14, height: 14 }} />
|
|
411
|
+
) : (
|
|
412
|
+
<XMarkIcon style={{ width: 14, height: 14 }} />
|
|
413
|
+
)}
|
|
414
|
+
</FeatureIcon>
|
|
415
|
+
<Typography
|
|
416
|
+
variant="omega"
|
|
417
|
+
style={{
|
|
418
|
+
fontSize: '14px',
|
|
419
|
+
color: feature.included ? '#374151' : '#9CA3AF',
|
|
420
|
+
textDecoration: feature.included ? 'none' : 'line-through'
|
|
421
|
+
}}
|
|
422
|
+
>
|
|
423
|
+
{feature.name}
|
|
424
|
+
</Typography>
|
|
425
|
+
</Feature>
|
|
426
|
+
))}
|
|
427
|
+
</FeatureList>
|
|
428
|
+
|
|
429
|
+
{currentTier === tier.id ? (
|
|
430
|
+
<CurrentPlanBadge>CURRENT PLAN</CurrentPlanBadge>
|
|
431
|
+
) : (
|
|
432
|
+
<UpgradeButton
|
|
433
|
+
onClick={() => handleUpgrade(tier.id)}
|
|
434
|
+
$gradient={tier.color}
|
|
435
|
+
>
|
|
436
|
+
{getButtonText(tier.id)}
|
|
437
|
+
</UpgradeButton>
|
|
438
|
+
)}
|
|
439
|
+
</TierCard>
|
|
440
|
+
</TierWrapper>
|
|
441
|
+
))}
|
|
442
|
+
</TierGrid>
|
|
443
|
+
</Container>
|
|
444
|
+
);
|
|
445
|
+
};
|
|
446
|
+
|
|
447
|
+
export default UpgradePage;
|
|
448
|
+
|
package/admin/src/pluginId.js
CHANGED