strapi-plugin-magic-sessionmanager 4.2.4 → 4.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/dist/server/index.js +1 -1
  2. package/dist/server/index.mjs +1 -1
  3. package/package.json +1 -3
  4. package/admin/jsconfig.json +0 -10
  5. package/admin/src/components/Initializer.jsx +0 -11
  6. package/admin/src/components/LicenseGuard.jsx +0 -591
  7. package/admin/src/components/OnlineUsersWidget.jsx +0 -212
  8. package/admin/src/components/PluginIcon.jsx +0 -8
  9. package/admin/src/components/SessionDetailModal.jsx +0 -449
  10. package/admin/src/components/SessionInfoCard.jsx +0 -151
  11. package/admin/src/components/SessionInfoPanel.jsx +0 -385
  12. package/admin/src/components/index.jsx +0 -5
  13. package/admin/src/hooks/useLicense.js +0 -103
  14. package/admin/src/index.js +0 -149
  15. package/admin/src/pages/ActiveSessions.jsx +0 -12
  16. package/admin/src/pages/Analytics.jsx +0 -735
  17. package/admin/src/pages/App.jsx +0 -12
  18. package/admin/src/pages/HomePage.jsx +0 -1212
  19. package/admin/src/pages/License.jsx +0 -603
  20. package/admin/src/pages/Settings.jsx +0 -1646
  21. package/admin/src/pages/SettingsNew.jsx +0 -1204
  22. package/admin/src/pages/UpgradePage.jsx +0 -448
  23. package/admin/src/pages/index.jsx +0 -3
  24. package/admin/src/pluginId.js +0 -4
  25. package/admin/src/translations/de.json +0 -299
  26. package/admin/src/translations/en.json +0 -299
  27. package/admin/src/translations/es.json +0 -287
  28. package/admin/src/translations/fr.json +0 -287
  29. package/admin/src/translations/pt.json +0 -287
  30. package/admin/src/utils/getTranslation.js +0 -5
  31. package/admin/src/utils/index.js +0 -2
  32. package/admin/src/utils/parseUserAgent.js +0 -79
  33. package/admin/src/utils/theme.js +0 -85
  34. package/server/jsconfig.json +0 -10
  35. package/server/src/bootstrap.js +0 -492
  36. package/server/src/config/index.js +0 -23
  37. package/server/src/content-types/index.js +0 -9
  38. package/server/src/content-types/session/schema.json +0 -84
  39. package/server/src/controllers/controller.js +0 -11
  40. package/server/src/controllers/index.js +0 -11
  41. package/server/src/controllers/license.js +0 -266
  42. package/server/src/controllers/session.js +0 -433
  43. package/server/src/controllers/settings.js +0 -122
  44. package/server/src/destroy.js +0 -22
  45. package/server/src/index.js +0 -23
  46. package/server/src/middlewares/index.js +0 -5
  47. package/server/src/middlewares/last-seen.js +0 -62
  48. package/server/src/policies/index.js +0 -3
  49. package/server/src/register.js +0 -36
  50. package/server/src/routes/admin.js +0 -149
  51. package/server/src/routes/content-api.js +0 -60
  52. package/server/src/routes/index.js +0 -9
  53. package/server/src/services/geolocation.js +0 -182
  54. package/server/src/services/index.js +0 -13
  55. package/server/src/services/license-guard.js +0 -316
  56. package/server/src/services/notifications.js +0 -319
  57. package/server/src/services/service.js +0 -7
  58. package/server/src/services/session.js +0 -393
  59. package/server/src/utils/encryption.js +0 -121
  60. package/server/src/utils/getClientIp.js +0 -118
  61. package/server/src/utils/logger.js +0 -84
@@ -1,735 +0,0 @@
1
- import { useState, useEffect } from 'react';
2
- import { useFetchClient } from '@strapi/strapi/admin';
3
- import styled, { keyframes, css } from 'styled-components';
4
- import {
5
- Box,
6
- Typography,
7
- Flex,
8
- Loader,
9
- Badge,
10
- } from '@strapi/design-system';
11
- import {
12
- ChartBubble,
13
- User,
14
- Monitor,
15
- Clock,
16
- Crown,
17
- } from '@strapi/icons';
18
- import pluginId from '../pluginId';
19
- import { useLicense } from '../hooks/useLicense';
20
-
21
- // ================ THEME ================
22
- const theme = {
23
- colors: {
24
- primary: { 50: '#F0F9FF', 100: '#E0F2FE', 500: '#0EA5E9', 600: '#0284C7', 700: '#0369A1' },
25
- secondary: { 50: '#F5F3FF', 100: '#EDE9FE', 500: '#A855F7', 600: '#9333EA' },
26
- success: { 50: '#DCFCE7', 100: '#DCFCE7', 500: '#22C55E', 600: '#16A34A', 700: '#15803D' },
27
- warning: { 50: '#FEF3C7', 100: '#FEF3C7', 500: '#F59E0B', 600: '#D97706' },
28
- danger: { 50: '#FEE2E2', 100: '#FEE2E2', 500: '#EF4444', 600: '#DC2626' },
29
- neutral: { 0: '#FFFFFF', 50: '#F9FAFB', 100: '#F3F4F6', 200: '#E5E7EB', 600: '#4B5563', 700: '#374151', 800: '#1F2937' }
30
- },
31
- shadows: {
32
- sm: '0 1px 3px 0 rgba(0, 0, 0, 0.1)',
33
- md: '0 4px 6px -1px rgba(0, 0, 0, 0.1)',
34
- lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1)',
35
- xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1)',
36
- },
37
- transitions: {
38
- fast: '150ms cubic-bezier(0.4, 0, 0.2, 1)',
39
- normal: '300ms cubic-bezier(0.4, 0, 0.2, 1)',
40
- slow: '500ms cubic-bezier(0.4, 0, 0.2, 1)',
41
- },
42
- spacing: { xl: '32px', '2xl': '48px' },
43
- borderRadius: { lg: '12px', xl: '16px' },
44
- };
45
-
46
- // ================ ANIMATIONS ================
47
- const fadeIn = keyframes`
48
- from { opacity: 0; transform: translateY(20px); }
49
- to { opacity: 1; transform: translateY(0); }
50
- `;
51
-
52
- const slideIn = keyframes`
53
- from { opacity: 0; transform: translateX(-20px); }
54
- to { opacity: 1; transform: translateX(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(-8px); }
65
- `;
66
-
67
- const pulse = keyframes`
68
- 0%, 100% { opacity: 1; }
69
- 50% { opacity: 0.8; }
70
- `;
71
-
72
- const growBar = keyframes`
73
- from { width: 0; }
74
- to { width: var(--bar-width); }
75
- `;
76
-
77
- // ================ STYLED COMPONENTS ================
78
- const Container = styled(Box)`
79
- ${css`animation: ${fadeIn} 0.6s;`}
80
- min-height: 100vh;
81
- max-width: 1440px;
82
- margin: 0 auto;
83
- padding: ${theme.spacing.xl} 24px 0;
84
- `;
85
-
86
- const Header = styled(Box)`
87
- background: linear-gradient(135deg, ${theme.colors.primary[600]} 0%, ${theme.colors.secondary[600]} 100%);
88
- border-radius: ${theme.borderRadius.xl};
89
- padding: ${theme.spacing.xl} ${theme.spacing['2xl']};
90
- margin-bottom: ${theme.spacing.xl};
91
- position: relative;
92
- overflow: hidden;
93
- box-shadow: ${theme.shadows.xl};
94
-
95
- &::before {
96
- content: '';
97
- position: absolute;
98
- top: 0;
99
- left: -100%;
100
- width: 200%;
101
- height: 100%;
102
- background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.15), transparent);
103
- ${css`animation: ${shimmer} 3s infinite;`}
104
- }
105
-
106
- &::after {
107
- content: '';
108
- position: absolute;
109
- top: 0;
110
- right: 0;
111
- width: 100%;
112
- height: 100%;
113
- background-image: radial-gradient(circle at 20% 80%, transparent 50%, rgba(255, 255, 255, 0.1) 50%);
114
- background-size: 15px 15px;
115
- opacity: 0.3;
116
- }
117
- `;
118
-
119
- const HeaderContent = styled(Flex)`
120
- position: relative;
121
- z-index: 1;
122
- `;
123
-
124
- const Title = styled(Typography)`
125
- color: ${theme.colors.neutral[0]};
126
- font-size: 2.25rem;
127
- font-weight: 700;
128
- letter-spacing: -0.025em;
129
- text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
130
- display: flex;
131
- align-items: center;
132
- gap: 16px;
133
- margin-bottom: 8px;
134
-
135
- svg {
136
- width: 32px;
137
- height: 32px;
138
- ${css`animation: ${float} 3s ease-in-out infinite;`}
139
- }
140
- `;
141
-
142
- const Subtitle = styled(Typography)`
143
- color: rgba(255, 255, 255, 0.95);
144
- font-size: 1rem;
145
- font-weight: 400;
146
- letter-spacing: 0.01em;
147
- `;
148
-
149
- const StatsGrid = styled.div`
150
- display: grid;
151
- grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
152
- gap: 24px;
153
- margin-bottom: 40px;
154
- `;
155
-
156
- const StatCard = styled(Box)`
157
- background: ${theme.colors.neutral[0]};
158
- border-radius: ${theme.borderRadius.lg};
159
- padding: 32px;
160
- position: relative;
161
- overflow: hidden;
162
- transition: all ${theme.transitions.normal};
163
- ${css`animation: ${fadeIn} ${theme.transitions.slow} backwards;`}
164
- animation-delay: ${props => props.$delay || '0s'};
165
- box-shadow: ${theme.shadows.sm};
166
- border: 1px solid ${theme.colors.neutral[200]};
167
- text-align: center;
168
- display: flex;
169
- flex-direction: column;
170
- align-items: center;
171
- justify-content: center;
172
-
173
- &:hover {
174
- transform: translateY(-6px);
175
- box-shadow: ${theme.shadows.xl};
176
- border-color: ${props => props.$borderColor || theme.colors.primary[500]};
177
-
178
- .stat-icon {
179
- transform: scale(1.15) rotate(5deg);
180
- }
181
-
182
- .stat-value {
183
- transform: scale(1.08);
184
- color: ${props => props.$accentColor || theme.colors.primary[600]};
185
- }
186
- }
187
- `;
188
-
189
- const StatIcon = styled(Box)`
190
- width: 80px;
191
- height: 80px;
192
- border-radius: ${theme.borderRadius.lg};
193
- display: flex;
194
- align-items: center;
195
- justify-content: center;
196
- background: ${props => props.$bg || theme.colors.primary[100]};
197
- transition: all ${theme.transitions.normal};
198
- margin: 0 auto 24px;
199
- box-shadow: ${theme.shadows.sm};
200
-
201
- svg {
202
- width: 40px;
203
- height: 40px;
204
- color: ${props => props.$color || theme.colors.primary[600]};
205
- }
206
- `;
207
-
208
- const StatValue = styled(Typography)`
209
- font-size: 3.5rem;
210
- font-weight: 700;
211
- color: ${theme.colors.neutral[800]};
212
- line-height: 1;
213
- margin-bottom: 12px;
214
- transition: all ${theme.transitions.normal};
215
- text-align: center;
216
- `;
217
-
218
- const StatLabel = styled(Typography)`
219
- font-size: 1rem;
220
- color: ${theme.colors.neutral[600]};
221
- font-weight: 500;
222
- text-align: center;
223
- `;
224
-
225
- const ChartCard = styled(Box)`
226
- background: ${theme.colors.neutral[0]};
227
- border-radius: ${theme.borderRadius.lg};
228
- padding: 36px;
229
- box-shadow: ${theme.shadows.md};
230
- border: 1px solid ${theme.colors.neutral[200]};
231
- margin-bottom: 28px;
232
- ${css`animation: ${slideIn} ${theme.transitions.slow};`}
233
- transition: all ${theme.transitions.normal};
234
-
235
- &:hover {
236
- box-shadow: ${theme.shadows.lg};
237
- border-color: ${theme.colors.primary[200]};
238
- }
239
- `;
240
-
241
- const ChartTitle = styled(Typography)`
242
- font-size: 1.25rem;
243
- font-weight: 700;
244
- color: ${theme.colors.neutral[800]};
245
- margin-bottom: 24px;
246
- display: flex;
247
- align-items: center;
248
- gap: 12px;
249
-
250
- svg {
251
- width: 24px;
252
- height: 24px;
253
- color: ${theme.colors.primary[600]};
254
- }
255
- `;
256
-
257
- const BarChart = styled.div`
258
- display: flex;
259
- flex-direction: column;
260
- gap: 20px;
261
- `;
262
-
263
- const BarRow = styled.div`
264
- display: flex;
265
- align-items: center;
266
- gap: 20px;
267
- ${css`animation: ${fadeIn} 0.6s backwards;`}
268
- animation-delay: ${props => props.$delay || '0s'};
269
- `;
270
-
271
- const BarLabel = styled(Typography)`
272
- min-width: 110px;
273
- font-size: 15px;
274
- font-weight: 600;
275
- color: ${theme.colors.neutral[700]};
276
- `;
277
-
278
- const BarContainer = styled.div`
279
- flex: 1;
280
- height: 40px;
281
- background: ${theme.colors.neutral[100]};
282
- border-radius: 10px;
283
- overflow: hidden;
284
- position: relative;
285
- box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.06);
286
- `;
287
-
288
- const BarFill = styled.div`
289
- height: 100%;
290
- background: linear-gradient(90deg, ${props => props.$color1 || theme.colors.primary[500]}, ${props => props.$color2 || theme.colors.primary[600]});
291
- border-radius: 10px;
292
- --bar-width: ${props => props.$percentage || 0}%;
293
- ${css`animation: ${growBar} 1s cubic-bezier(0.4, 0, 0.2, 1) forwards;`}
294
- animation-delay: ${props => props.$delay || '0s'};
295
- display: flex;
296
- align-items: center;
297
- justify-content: flex-end;
298
- padding-right: 16px;
299
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
300
- `;
301
-
302
- const BarValue = styled(Typography)`
303
- color: white;
304
- font-size: 15px;
305
- font-weight: 700;
306
- text-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
307
- `;
308
-
309
- const LoadingOverlay = styled.div`
310
- display: flex;
311
- flex-direction: column;
312
- align-items: center;
313
- justify-content: center;
314
- min-height: 400px;
315
- gap: 24px;
316
-
317
- .loader-icon {
318
- ${css`animation: ${pulse} 2s ease-in-out infinite;`}
319
- }
320
- `;
321
-
322
- const AnimatedIcon = styled.div`
323
- ${css`animation: ${float} 3s ease-in-out infinite;`}
324
- width: 96px;
325
- height: 96px;
326
- color: #d97706;
327
- margin: 0 auto 32px;
328
- display: flex;
329
- align-items: center;
330
- justify-content: center;
331
- `;
332
-
333
- const AnalyticsPage = () => {
334
- const { get } = useFetchClient();
335
- const { isPremium, loading: licenseLoading } = useLicense();
336
- const [loading, setLoading] = useState(true);
337
- const [analytics, setAnalytics] = useState(null);
338
-
339
- useEffect(() => {
340
- if (!licenseLoading) {
341
- fetchAnalytics();
342
- }
343
- }, [licenseLoading]);
344
-
345
- const fetchAnalytics = async () => {
346
- setLoading(true);
347
- try {
348
- const { data } = await get(`/${pluginId}/sessions`);
349
- const sessions = data.data || [];
350
-
351
- const now = Date.now();
352
- const dayAgo = now - (24 * 60 * 60 * 1000);
353
- const weekAgo = now - (7 * 24 * 60 * 60 * 1000);
354
-
355
- const todayLogins = sessions.filter(s => new Date(s.loginTime).getTime() > dayAgo);
356
- const weekLogins = sessions.filter(s => new Date(s.loginTime).getTime() > weekAgo);
357
-
358
- const devices = {};
359
- const browsers = {};
360
- const operatingSystems = {};
361
- const countries = {};
362
- const loginHours = Array(24).fill(0);
363
- const uniqueUsers = new Set();
364
- const uniqueIPs = new Set();
365
-
366
- sessions.forEach(session => {
367
- const ua = session.userAgent.toLowerCase();
368
-
369
- // Devices
370
- if (ua.includes('mobile') || ua.includes('android') || ua.includes('iphone')) {
371
- devices['Mobile'] = (devices['Mobile'] || 0) + 1;
372
- } else if (ua.includes('tablet') || ua.includes('ipad')) {
373
- devices['Tablet'] = (devices['Tablet'] || 0) + 1;
374
- } else {
375
- devices['Desktop'] = (devices['Desktop'] || 0) + 1;
376
- }
377
-
378
- // Browsers
379
- if (ua.includes('chrome') && !ua.includes('edg')) browsers['Chrome'] = (browsers['Chrome'] || 0) + 1;
380
- else if (ua.includes('firefox')) browsers['Firefox'] = (browsers['Firefox'] || 0) + 1;
381
- else if (ua.includes('safari') && !ua.includes('chrome')) browsers['Safari'] = (browsers['Safari'] || 0) + 1;
382
- else if (ua.includes('edg')) browsers['Edge'] = (browsers['Edge'] || 0) + 1;
383
- else if (ua.includes('postman') || ua.includes('curl')) browsers['API Client'] = (browsers['API Client'] || 0) + 1;
384
- else browsers['Other'] = (browsers['Other'] || 0) + 1;
385
-
386
- // Operating Systems
387
- if (ua.includes('windows') || ua.includes('win32') || ua.includes('win64')) {
388
- operatingSystems['Windows'] = (operatingSystems['Windows'] || 0) + 1;
389
- } else if (ua.includes('mac') || ua.includes('darwin')) {
390
- operatingSystems['macOS'] = (operatingSystems['macOS'] || 0) + 1;
391
- } else if (ua.includes('linux')) {
392
- operatingSystems['Linux'] = (operatingSystems['Linux'] || 0) + 1;
393
- } else if (ua.includes('android')) {
394
- operatingSystems['Android'] = (operatingSystems['Android'] || 0) + 1;
395
- } else if (ua.includes('ios') || ua.includes('iphone') || ua.includes('ipad')) {
396
- operatingSystems['iOS'] = (operatingSystems['iOS'] || 0) + 1;
397
- } else {
398
- operatingSystems['Other'] = (operatingSystems['Other'] || 0) + 1;
399
- }
400
-
401
- // Login Hours (24h distribution)
402
- const loginHour = new Date(session.loginTime).getHours();
403
- loginHours[loginHour]++;
404
-
405
- // Unique tracking
406
- if (session.user?.id) uniqueUsers.add(session.user.id);
407
- if (session.ipAddress) uniqueIPs.add(session.ipAddress);
408
- });
409
-
410
- // Calculate peak hour
411
- const peakHour = loginHours.indexOf(Math.max(...loginHours));
412
-
413
- // Calculate logout vs timeout ratio
414
- const loggedOut = sessions.filter(s => !s.isActive && s.logoutTime).length;
415
- const terminated = sessions.filter(s => !s.isActive && !s.logoutTime).length;
416
-
417
- // Calculate mobile vs desktop ratio
418
- const mobileCount = (devices['Mobile'] || 0) + (devices['Tablet'] || 0);
419
- const desktopCount = devices['Desktop'] || 0;
420
- const mobileRatio = sessions.length > 0 ? Math.round((mobileCount / sessions.length) * 100) : 0;
421
-
422
- setAnalytics({
423
- totalSessions: sessions.length,
424
- activeSessions: sessions.filter(s => s.isActive && s.isTrulyActive).length,
425
- todayLogins: todayLogins.length,
426
- weekLogins: weekLogins.length,
427
- devices,
428
- browsers,
429
- operatingSystems,
430
- loginHours,
431
- peakHour,
432
- uniqueUsers: uniqueUsers.size,
433
- uniqueIPs: uniqueIPs.size,
434
- loggedOut,
435
- terminated,
436
- mobileRatio,
437
- avgSessionDuration: sessions.length > 0
438
- ? Math.floor(sessions.reduce((sum, s) => sum + (s.minutesSinceActive || 0), 0) / sessions.length)
439
- : 0,
440
- });
441
- } catch (err) {
442
- console.error('[Analytics] Error:', err);
443
- } finally {
444
- setLoading(false);
445
- }
446
- };
447
-
448
- // Loading state während License-Check
449
- if (licenseLoading) {
450
- return (
451
- <Container>
452
- <LoadingOverlay>
453
- <ChartBubble className="loader-icon" style={{ width: '64px', height: '64px', color: theme.colors.primary[600] }} />
454
- <Loader>Checking license...</Loader>
455
- <Typography variant="pi" textColor="neutral600">
456
- Please wait while we verify your premium access
457
- </Typography>
458
- </LoadingOverlay>
459
- </Container>
460
- );
461
- }
462
-
463
- // Upgrade Screen für Free-User
464
- if (!isPremium) {
465
- return (
466
- <Container>
467
- <Box padding={8}>
468
- <Box
469
- padding={10}
470
- style={{
471
- background: 'linear-gradient(135deg, #fef3c7 0%, #fed7aa 100%)',
472
- borderRadius: '20px',
473
- border: '3px solid #fbbf24',
474
- textAlign: 'center',
475
- boxShadow: '0 20px 40px rgba(245, 158, 11, 0.2)',
476
- maxWidth: '800px',
477
- margin: '60px auto',
478
- position: 'relative',
479
- overflow: 'hidden',
480
- }}
481
- >
482
- {/* Background Pattern */}
483
- <div style={{
484
- position: 'absolute',
485
- top: 0,
486
- left: 0,
487
- right: 0,
488
- bottom: 0,
489
- backgroundImage: 'radial-gradient(circle at 20% 80%, transparent 50%, rgba(255, 255, 255, 0.1) 50%)',
490
- backgroundSize: '20px 20px',
491
- opacity: 0.5,
492
- zIndex: 0,
493
- }} />
494
-
495
- <div style={{ position: 'relative', zIndex: 1 }}>
496
- <AnimatedIcon as={Crown} />
497
-
498
- <Typography
499
- variant="alpha"
500
- style={{
501
- color: '#92400e',
502
- fontWeight: '700',
503
- marginBottom: '24px',
504
- fontSize: '36px',
505
- display: 'block',
506
- textShadow: '0 2px 4px rgba(0, 0, 0, 0.1)',
507
- }}
508
- >
509
- Analytics Dashboard
510
- </Typography>
511
-
512
- <Typography
513
- variant="omega"
514
- style={{
515
- color: '#78350f',
516
- lineHeight: '1.9',
517
- marginBottom: '44px',
518
- fontSize: '17px',
519
- display: 'block',
520
- maxWidth: '620px',
521
- margin: '0 auto 44px',
522
- }}
523
- >
524
- Unlock premium analytics to get powerful insights about your user sessions, device statistics, browser trends, and activity patterns
525
- </Typography>
526
-
527
- <button
528
- onClick={() => window.open('https://magicapi.fitlex.me', '_blank')}
529
- style={{
530
- background: 'linear-gradient(135deg, #f59e0b 0%, #d97706 100%)',
531
- color: 'white',
532
- border: 'none',
533
- padding: '16px 48px',
534
- borderRadius: '12px',
535
- fontSize: '17px',
536
- fontWeight: '700',
537
- cursor: 'pointer',
538
- boxShadow: '0 6px 16px rgba(245, 158, 11, 0.4)',
539
- transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
540
- }}
541
- onMouseEnter={(e) => {
542
- e.currentTarget.style.transform = 'translateY(-3px) scale(1.05)';
543
- e.currentTarget.style.boxShadow = '0 12px 24px rgba(245, 158, 11, 0.5)';
544
- }}
545
- onMouseLeave={(e) => {
546
- e.currentTarget.style.transform = 'translateY(0) scale(1)';
547
- e.currentTarget.style.boxShadow = '0 6px 16px rgba(245, 158, 11, 0.4)';
548
- }}
549
- >
550
- Upgrade to Premium
551
- </button>
552
- </div>
553
- </Box>
554
- </Box>
555
- </Container>
556
- );
557
- }
558
-
559
- // Loading analytics data
560
- if (loading) {
561
- return (
562
- <Container>
563
- <LoadingOverlay>
564
- <ChartBubble className="loader-icon" style={{ width: '64px', height: '64px', color: theme.colors.primary[600] }} />
565
- <Loader>Loading analytics data...</Loader>
566
- </LoadingOverlay>
567
- </Container>
568
- );
569
- }
570
-
571
- const maxDevices = Math.max(...Object.values(analytics?.devices || {}), 1);
572
- const maxBrowsers = Math.max(...Object.values(analytics?.browsers || {}), 1);
573
- const maxOS = Math.max(...Object.values(analytics?.operatingSystems || {}), 1);
574
- const maxLoginHour = Math.max(...(analytics?.loginHours || []), 1);
575
-
576
- const deviceColors = {
577
- 'Desktop': [theme.colors.primary[500], theme.colors.primary[600]],
578
- 'Mobile': [theme.colors.success[500], theme.colors.success[600]],
579
- 'Tablet': [theme.colors.warning[500], theme.colors.warning[600]],
580
- };
581
-
582
- const browserColors = {
583
- 'Chrome': [theme.colors.success[500], theme.colors.success[600]],
584
- 'Firefox': [theme.colors.warning[500], theme.colors.warning[600]],
585
- 'Safari': [theme.colors.primary[500], theme.colors.primary[600]],
586
- 'Edge': [theme.colors.secondary[500], theme.colors.secondary[600]],
587
- 'API Client': [theme.colors.neutral[600], theme.colors.neutral[700]],
588
- 'Other': [theme.colors.neutral[500], theme.colors.neutral[600]],
589
- };
590
-
591
- return (
592
- <Container>
593
- {/* Gradient Header */}
594
- <Header>
595
- <HeaderContent direction="column" alignItems="flex-start" gap={2}>
596
- <Title>
597
- <ChartBubble /> Session Analytics
598
- </Title>
599
- <Subtitle>
600
- Comprehensive insights and statistics about user sessions
601
- </Subtitle>
602
- </HeaderContent>
603
- </Header>
604
-
605
- {/* Overview Stats */}
606
- <StatsGrid>
607
- <StatCard $delay="0.1s" $borderColor={theme.colors.primary[500]} $accentColor={theme.colors.primary[600]}>
608
- <StatIcon className="stat-icon" $bg={theme.colors.primary[100]} $color={theme.colors.primary[600]}>
609
- <ChartBubble />
610
- </StatIcon>
611
- <StatValue className="stat-value">{analytics?.totalSessions || 0}</StatValue>
612
- <StatLabel>Total Sessions</StatLabel>
613
- </StatCard>
614
-
615
- <StatCard $delay="0.2s" $borderColor={theme.colors.success[500]} $accentColor={theme.colors.success[600]}>
616
- <StatIcon className="stat-icon" $bg={theme.colors.success[100]} $color={theme.colors.success[600]}>
617
- <User />
618
- </StatIcon>
619
- <StatValue className="stat-value">{analytics?.activeSessions || 0}</StatValue>
620
- <StatLabel>Active Now</StatLabel>
621
- </StatCard>
622
-
623
- <StatCard $delay="0.3s" $borderColor={theme.colors.warning[500]} $accentColor={theme.colors.warning[600]}>
624
- <StatIcon className="stat-icon" $bg={theme.colors.warning[100]} $color={theme.colors.warning[600]}>
625
- <Clock />
626
- </StatIcon>
627
- <StatValue className="stat-value">{analytics?.todayLogins || 0}</StatValue>
628
- <StatLabel>Today's Logins</StatLabel>
629
- </StatCard>
630
-
631
- <StatCard $delay="0.4s" $borderColor={theme.colors.secondary[500]} $accentColor={theme.colors.secondary[600]}>
632
- <StatIcon className="stat-icon" $bg={theme.colors.secondary[100]} $color={theme.colors.secondary[600]}>
633
- <Clock />
634
- </StatIcon>
635
- <StatValue className="stat-value">{analytics?.weekLogins || 0}</StatValue>
636
- <StatLabel>This Week</StatLabel>
637
- </StatCard>
638
- </StatsGrid>
639
-
640
- {/* Charts Row */}
641
- <Flex gap={4} wrap="wrap" style={{ marginBottom: '28px' }}>
642
- {/* Devices Chart */}
643
- <Box style={{ flex: 1, minWidth: '450px' }}>
644
- <ChartCard>
645
- <ChartTitle>
646
- <Monitor />
647
- Device Distribution
648
- </ChartTitle>
649
- <BarChart>
650
- {analytics?.devices && Object.entries(analytics.devices)
651
- .sort(([, a], [, b]) => b - a)
652
- .map(([device, count], idx) => (
653
- <BarRow key={device} $delay={`${0.5 + idx * 0.1}s`}>
654
- <BarLabel>{device}</BarLabel>
655
- <BarContainer>
656
- <BarFill
657
- $percentage={(count / maxDevices) * 100}
658
- $color1={deviceColors[device]?.[0] || theme.colors.neutral[500]}
659
- $color2={deviceColors[device]?.[1] || theme.colors.neutral[600]}
660
- $delay={`${0.5 + idx * 0.1}s`}
661
- >
662
- <BarValue>{count}</BarValue>
663
- </BarFill>
664
- </BarContainer>
665
- </BarRow>
666
- ))}
667
- </BarChart>
668
- </ChartCard>
669
- </Box>
670
-
671
- {/* Browsers Chart */}
672
- <Box style={{ flex: 1, minWidth: '450px' }}>
673
- <ChartCard>
674
- <ChartTitle>
675
- <Monitor />
676
- Browser Usage
677
- </ChartTitle>
678
- <BarChart>
679
- {analytics?.browsers && Object.entries(analytics.browsers)
680
- .sort(([, a], [, b]) => b - a)
681
- .map(([browser, count], idx) => (
682
- <BarRow key={browser} $delay={`${0.5 + idx * 0.1}s`}>
683
- <BarLabel>{browser}</BarLabel>
684
- <BarContainer>
685
- <BarFill
686
- $percentage={(count / maxBrowsers) * 100}
687
- $color1={browserColors[browser]?.[0] || theme.colors.neutral[500]}
688
- $color2={browserColors[browser]?.[1] || theme.colors.neutral[600]}
689
- $delay={`${0.5 + idx * 0.1}s`}
690
- >
691
- <BarValue>{count}</BarValue>
692
- </BarFill>
693
- </BarContainer>
694
- </BarRow>
695
- ))}
696
- </BarChart>
697
- </ChartCard>
698
- </Box>
699
- </Flex>
700
-
701
- {/* Average Session Duration */}
702
- <ChartCard>
703
- <Flex alignItems="center" justifyContent="space-between">
704
- <ChartTitle style={{ marginBottom: 0 }}>
705
- <Clock />
706
- Average Session Duration
707
- </ChartTitle>
708
- <Badge
709
- backgroundColor="primary600"
710
- textColor="neutral0"
711
- style={{
712
- fontSize: '18px',
713
- fontWeight: '700',
714
- padding: '12px 24px',
715
- boxShadow: theme.shadows.md,
716
- }}
717
- >
718
- {analytics?.avgSessionDuration || 0} minutes
719
- </Badge>
720
- </Flex>
721
-
722
- {analytics?.avgSessionDuration > 0 && (
723
- <Box marginTop={5} padding={5} background="primary50" hasRadius style={{ border: `1px solid ${theme.colors.primary[100]}` }}>
724
- <Typography variant="omega" textColor="primary700" style={{ fontSize: '14px', lineHeight: '1.8', fontWeight: '500' }}>
725
- ℹ️ Average time between login and last activity across all sessions.
726
- Lower values indicate more frequent activity, higher values may indicate idle or abandoned sessions.
727
- </Typography>
728
- </Box>
729
- )}
730
- </ChartCard>
731
- </Container>
732
- );
733
- };
734
-
735
- export default AnalyticsPage;