strapi-plugin-magic-link-v5 4.4.1 → 4.4.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/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "4.4.1",
2
+ "version": "4.4.2",
3
3
  "keywords": [],
4
4
  "type": "commonjs",
5
5
  "exports": {
@@ -61,12 +61,18 @@ module.exports = {
61
61
  orderBy: { createdAt: 'desc' },
62
62
  });
63
63
 
64
+ // SQLite speichert Booleans als 0/1 - konvertiere zu echten Booleans
65
+ const normalizedTokens = tokens.map(token => ({
66
+ ...token,
67
+ is_active: !!token.is_active // Konvertiere 0/1 zu false/true
68
+ }));
69
+
64
70
  // Berechne den Sicherheitswert
65
71
  const securityScore = await this.calculateSecurityScore();
66
72
 
67
73
  // Füge den Sicherheitswert als Metadaten hinzu
68
74
  return {
69
- data: tokens,
75
+ data: normalizedTokens,
70
76
  meta: {
71
77
  securityScore
72
78
  }
@@ -1,807 +0,0 @@
1
- import React, { useState, useEffect } from 'react';
2
- import styled, { keyframes } from 'styled-components';
3
- import {
4
- Main,
5
- Typography,
6
- Box,
7
- Flex,
8
- Button,
9
- Loader,
10
- EmptyStateLayout,
11
- Table,
12
- Thead,
13
- Tbody,
14
- Tr,
15
- Td,
16
- Th,
17
- Badge,
18
- IconButton,
19
- Grid,
20
- Tooltip,
21
- Modal,
22
- TextInput,
23
- Field,
24
- Checkbox,
25
- } from '@strapi/design-system';
26
- import { useFetchClient, useNotification } from '@strapi/strapi/admin';
27
- import {
28
- Plus,
29
- ArrowLeft,
30
- ArrowRight,
31
- Key,
32
- Shield,
33
- Earth,
34
- PaintBrush,
35
- ChartBubble,
36
- Server,
37
- Clock,
38
- Eye,
39
- Trash,
40
- Mail,
41
- Cross,
42
- WarningCircle,
43
- Check,
44
- Pencil,
45
- User,
46
- Calendar,
47
- Lock,
48
- ArrowClockwise
49
- } from '@strapi/icons';
50
- import CreateTokenModal from './CreateTokenModal';
51
-
52
- // ================ ANIMATIONEN ================
53
- const fadeIn = keyframes`
54
- from { opacity: 0; transform: translateY(20px); }
55
- to { opacity: 1; transform: translateY(0); }
56
- `;
57
-
58
- const pulse = keyframes`
59
- 0%, 100% { transform: scale(1); }
60
- 50% { transform: scale(1.05); }
61
- `;
62
-
63
- const shimmer = keyframes`
64
- 0% { background-position: -1000px 0; }
65
- 100% { background-position: 1000px 0; }
66
- `;
67
-
68
- const rotate = keyframes`
69
- from { transform: rotate(0deg); }
70
- to { transform: rotate(360deg); }
71
- `;
72
-
73
- // ================ STYLED COMPONENTS ================
74
- const PageWrapper = styled(Box)`
75
- animation: ${fadeIn} 0.6s ease;
76
- `;
77
-
78
- const GradientHeader = styled(Box)`
79
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
80
- border-radius: 24px;
81
- padding: 40px;
82
- margin-bottom: 32px;
83
- position: relative;
84
- overflow: hidden;
85
- box-shadow: 0 20px 40px -12px rgba(102, 126, 234, 0.35);
86
-
87
- &::before {
88
- content: '';
89
- position: absolute;
90
- top: 0;
91
- left: -100%;
92
- width: 200%;
93
- height: 100%;
94
- background: linear-gradient(
95
- 90deg,
96
- transparent,
97
- rgba(255, 255, 255, 0.1),
98
- transparent
99
- );
100
- animation: ${shimmer} 3s infinite;
101
- }
102
-
103
- &::after {
104
- content: '✨';
105
- position: absolute;
106
- top: 20px;
107
- right: 20px;
108
- font-size: 48px;
109
- opacity: 0.1;
110
- animation: ${pulse} 2s infinite;
111
- }
112
- `;
113
-
114
- const HeaderTitle = styled(Typography)`
115
- color: white;
116
- font-size: 36px;
117
- font-weight: 700;
118
- margin-bottom: 8px;
119
- text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
120
- `;
121
-
122
- const HeaderSubtitle = styled(Typography)`
123
- color: rgba(255, 255, 255, 0.9);
124
- font-size: 16px;
125
- font-weight: 400;
126
- `;
127
-
128
- const GlassButton = styled(Button)`
129
- background: rgba(255, 255, 255, 0.2);
130
- backdrop-filter: blur(10px);
131
- border: 1px solid rgba(255, 255, 255, 0.3);
132
- color: white;
133
- font-weight: 600;
134
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
135
-
136
- &:hover {
137
- background: rgba(255, 255, 255, 0.3);
138
- transform: translateY(-2px);
139
- box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
140
- }
141
-
142
- &:active {
143
- transform: translateY(0);
144
- }
145
- `;
146
-
147
- const StatsGrid = styled(Grid.Root)`
148
- margin-bottom: 32px;
149
- `;
150
-
151
- const StatsCard = styled(Box)`
152
- background: rgba(255, 255, 255, 0.95);
153
- backdrop-filter: blur(20px);
154
- border: 1px solid rgba(255, 255, 255, 0.3);
155
- border-radius: 20px;
156
- padding: 28px;
157
- position: relative;
158
- overflow: hidden;
159
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
160
- animation: ${fadeIn} 0.6s ease backwards;
161
- animation-delay: ${props => props.$delay || '0s'};
162
-
163
- &:hover {
164
- transform: translateY(-8px) scale(1.02);
165
- box-shadow:
166
- 0 20px 40px -12px rgba(0, 0, 0, 0.15),
167
- 0 0 0 1px rgba(102, 126, 234, 0.1);
168
-
169
- .icon-wrapper {
170
- transform: rotate(-5deg) scale(1.1);
171
- }
172
-
173
- .stat-value {
174
- transform: scale(1.1);
175
- }
176
- }
177
-
178
- &::before {
179
- content: '';
180
- position: absolute;
181
- top: -50%;
182
- right: -50%;
183
- width: 200%;
184
- height: 200%;
185
- background: radial-gradient(
186
- circle,
187
- ${props => props.$gradientColor || 'rgba(102, 126, 234, 0.08)'} 0%,
188
- transparent 70%
189
- );
190
- pointer-events: none;
191
- }
192
- `;
193
-
194
- const IconWrapper = styled(Box)`
195
- padding: 18px;
196
- border-radius: 18px;
197
- display: inline-flex;
198
- transition: all 0.3s ease;
199
- background: ${props => props.$bgColor || 'rgba(102, 126, 234, 0.1)'};
200
-
201
- svg {
202
- width: 32px;
203
- height: 32px;
204
- color: ${props => props.$iconColor || '#667eea'};
205
- }
206
- `;
207
-
208
- const StatValue = styled(Typography)`
209
- font-size: 42px;
210
- font-weight: 700;
211
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
212
- -webkit-background-clip: text;
213
- -webkit-text-fill-color: transparent;
214
- transition: transform 0.3s ease;
215
- margin: 16px 0 8px;
216
- `;
217
-
218
- const TabContainer = styled(Flex)`
219
- margin-bottom: 32px;
220
- padding: 8px;
221
- background: rgba(255, 255, 255, 0.5);
222
- backdrop-filter: blur(10px);
223
- border-radius: 16px;
224
- box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.07);
225
- `;
226
-
227
- const TabButton = styled(Button)`
228
- border-radius: 12px;
229
- font-weight: 600;
230
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
231
- position: relative;
232
- overflow: hidden;
233
-
234
- ${props => props.$active && `
235
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
236
- color: white;
237
- transform: scale(1.05);
238
- box-shadow: 0 8px 16px -4px rgba(102, 126, 234, 0.4);
239
-
240
- &:hover {
241
- transform: scale(1.08);
242
- }
243
- `}
244
-
245
- ${props => !props.$active && `
246
- background: transparent;
247
- color: #4a5568;
248
-
249
- &:hover {
250
- background: rgba(102, 126, 234, 0.08);
251
- transform: translateY(-2px);
252
- }
253
- `}
254
- `;
255
-
256
- const StyledTable = styled(Table)`
257
- border-radius: 20px;
258
- overflow: hidden;
259
- box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.07);
260
- background: white;
261
-
262
- thead {
263
- background: linear-gradient(135deg, #f7f9fc 0%, #f1f3f8 100%);
264
-
265
- th {
266
- font-weight: 600;
267
- color: #2d3748;
268
- border-bottom: 2px solid #e2e8f0;
269
- padding: 16px;
270
- }
271
- }
272
-
273
- tbody tr {
274
- transition: all 0.2s ease;
275
- border-bottom: 1px solid rgba(0, 0, 0, 0.04);
276
-
277
- &:hover {
278
- background: linear-gradient(90deg,
279
- rgba(102, 126, 234, 0.02) 0%,
280
- rgba(102, 126, 234, 0.04) 50%,
281
- rgba(102, 126, 234, 0.02) 100%
282
- );
283
- transform: translateX(4px);
284
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.04);
285
- }
286
-
287
- td {
288
- padding: 16px;
289
- }
290
- }
291
- `;
292
-
293
- const AnimatedBadge = styled(Badge)`
294
- animation: ${fadeIn} 0.4s ease;
295
- transition: all 0.2s ease;
296
- font-weight: 600;
297
-
298
- &:hover {
299
- transform: scale(1.1);
300
- box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
301
- }
302
- `;
303
-
304
- const EmptyStateWrapper = styled(Box)`
305
- background: linear-gradient(135deg, #f7f9fc 0%, #ffffff 100%);
306
- border-radius: 24px;
307
- padding: 80px 40px;
308
- text-align: center;
309
- position: relative;
310
- overflow: hidden;
311
-
312
- &::before {
313
- content: '🎯';
314
- position: absolute;
315
- top: 30px;
316
- right: 30px;
317
- font-size: 60px;
318
- opacity: 0.05;
319
- animation: ${rotate} 20s linear infinite;
320
- }
321
-
322
- &::after {
323
- content: '✨';
324
- position: absolute;
325
- bottom: 30px;
326
- left: 30px;
327
- font-size: 60px;
328
- opacity: 0.05;
329
- animation: ${pulse} 2s ease infinite;
330
- }
331
- `;
332
-
333
- const LoadingWrapper = styled(Flex)`
334
- min-height: 400px;
335
- justify-content: center;
336
- align-items: center;
337
-
338
- .loader {
339
- animation: ${rotate} 1s linear infinite;
340
- }
341
- `;
342
-
343
- const ActionIconButton = styled(IconButton)`
344
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
345
-
346
- &:hover {
347
- transform: scale(1.2) rotate(5deg);
348
- background: rgba(102, 126, 234, 0.1);
349
- }
350
- `;
351
-
352
- // ================ HAUPTKOMPONENTE ================
353
- const TokensRedesign = () => {
354
- const { get, post, del } = useFetchClient();
355
- const { toggleNotification } = useNotification();
356
-
357
- // States
358
- const [tokens, setTokens] = useState([]);
359
- const [isLoading, setIsLoading] = useState(true);
360
- const [activeTab, setActiveTab] = useState('magic-links');
361
- const [selectedTokens, setSelectedTokens] = useState([]);
362
- const [showCreateModal, setShowCreateModal] = useState(false);
363
- const [createFormData, setCreateFormData] = useState({
364
- email: '',
365
- ttl: 24,
366
- sendEmail: true
367
- });
368
-
369
- // Stats berechnen
370
- const stats = {
371
- total: tokens.length,
372
- active: tokens.filter(t => {
373
- const isExpired = t.expires_at && new Date(t.expires_at) < new Date();
374
- return t.is_active && !isExpired;
375
- }).length,
376
- expired: tokens.filter(t => {
377
- const isExpired = t.expires_at && new Date(t.expires_at) < new Date();
378
- return isExpired;
379
- }).length,
380
- used: tokens.filter(t => !t.is_active).length,
381
- };
382
-
383
- // Stat Cards Konfiguration
384
- const statCards = [
385
- {
386
- title: 'Alle Tokens',
387
- value: stats.total,
388
- icon: Server,
389
- bgColor: 'rgba(102, 126, 234, 0.1)',
390
- iconColor: '#667eea',
391
- delay: '0s'
392
- },
393
- {
394
- title: 'Aktive Tokens',
395
- value: stats.active,
396
- icon: ChartBubble,
397
- bgColor: 'rgba(52, 211, 153, 0.1)',
398
- iconColor: '#10b981',
399
- delay: '0.1s'
400
- },
401
- {
402
- title: 'Abgelaufene',
403
- value: stats.expired,
404
- icon: Clock,
405
- bgColor: 'rgba(147, 51, 234, 0.1)',
406
- iconColor: '#8b5cf6',
407
- delay: '0.2s'
408
- },
409
- {
410
- title: 'Verwendet',
411
- value: stats.used,
412
- icon: Check,
413
- bgColor: 'rgba(251, 146, 60, 0.1)',
414
- iconColor: '#f59e0b',
415
- delay: '0.3s'
416
- }
417
- ];
418
-
419
- // Fetch Tokens
420
- useEffect(() => {
421
- fetchTokens();
422
- }, []);
423
-
424
- const fetchTokens = async () => {
425
- setIsLoading(true);
426
- try {
427
- const response = await get('/magic-link/tokens');
428
- setTokens(response?.data?.data || response?.data || []);
429
- } catch (error) {
430
- console.error('Error fetching tokens:', error);
431
- toggleNotification({
432
- type: 'warning',
433
- message: 'Fehler beim Laden der Tokens'
434
- });
435
- } finally {
436
- setIsLoading(false);
437
- }
438
- };
439
-
440
- const handleRefresh = () => {
441
- fetchTokens();
442
- toggleNotification({
443
- type: 'success',
444
- message: 'Daten wurden aktualisiert! 🎉'
445
- });
446
- };
447
-
448
- const handleCreateToken = async () => {
449
- try {
450
- if (!createFormData.email) {
451
- toggleNotification({
452
- type: 'warning',
453
- message: 'Bitte gib eine E-Mail-Adresse ein'
454
- });
455
- return;
456
- }
457
-
458
- // API-Aufruf zum Erstellen eines neuen Tokens
459
- const response = await post('/magic-link/tokens', {
460
- email: createFormData.email,
461
- send_email: createFormData.sendEmail,
462
- context: {
463
- ttl: createFormData.ttl
464
- }
465
- });
466
-
467
- if (response?.data) {
468
- toggleNotification({
469
- type: 'success',
470
- message: `Token erfolgreich erstellt! 🎉 ${createFormData.sendEmail ? 'E-Mail wurde gesendet.' : ''}`,
471
- });
472
-
473
- // Modal schließen und Formular zurücksetzen
474
- setShowCreateModal(false);
475
- setCreateFormData({
476
- email: '',
477
- ttl: 24,
478
- sendEmail: true
479
- });
480
-
481
- // Token-Liste aktualisieren
482
- await fetchTokens();
483
- }
484
- } catch (error) {
485
- console.error('Error creating token:', error);
486
- toggleNotification({
487
- type: 'warning',
488
- message: error?.response?.data?.error?.message || 'Fehler beim Erstellen des Tokens'
489
- });
490
- }
491
- };
492
-
493
- const handleDelete = async (tokenId) => {
494
- try {
495
- await del(`/magic-link/tokens/${tokenId}`);
496
- await fetchTokens();
497
- toggleNotification({
498
- type: 'success',
499
- message: 'Token wurde gelöscht! 🗑️'
500
- });
501
- } catch (error) {
502
- toggleNotification({
503
- type: 'warning',
504
- message: 'Fehler beim Löschen des Tokens'
505
- });
506
- }
507
- };
508
-
509
- if (isLoading) {
510
- return (
511
- <Main>
512
- <LoadingWrapper>
513
- <Box className="loader">
514
- <Loader>Loading...</Loader>
515
- </Box>
516
- </LoadingWrapper>
517
- </Main>
518
- );
519
- }
520
-
521
- return (
522
- <Main>
523
- <PageWrapper paddingBottom={10}>
524
- {/* Gradient Header */}
525
- <GradientHeader>
526
- <Flex justifyContent="space-between" alignItems="center">
527
- <Box>
528
- <HeaderTitle as="h1">
529
- ✨ Token-Verwaltung
530
- </HeaderTitle>
531
- <HeaderSubtitle>
532
- Verwalte deine Magic Link Tokens und JWT-Sessions mit Style
533
- </HeaderSubtitle>
534
- </Box>
535
- <Flex gap={2}>
536
- <GlassButton
537
- onClick={() => setShowCreateModal(true)}
538
- startIcon={<PaintBrush />}
539
- size="L"
540
- >
541
- Token erstellen
542
- </GlassButton>
543
- <GlassButton
544
- onClick={handleRefresh}
545
- startIcon={<ArrowClockwise />}
546
- size="L"
547
- >
548
- Aktualisieren
549
- </GlassButton>
550
- <GlassButton
551
- onClick={() => window.history.back()}
552
- startIcon={<ArrowLeft />}
553
- size="L"
554
- >
555
- Zurück
556
- </GlassButton>
557
- </Flex>
558
- </Flex>
559
- </GradientHeader>
560
-
561
- {/* Tab Navigation */}
562
- <TabContainer gap={2} justifyContent="center">
563
- <TabButton
564
- $active={activeTab === 'magic-links'}
565
- onClick={() => setActiveTab('magic-links')}
566
- startIcon={<Key />}
567
- size="L"
568
- >
569
- Magic Link Tokens
570
- </TabButton>
571
- <TabButton
572
- $active={activeTab === 'jwt-sessions'}
573
- onClick={() => setActiveTab('jwt-sessions')}
574
- startIcon={<Shield />}
575
- size="L"
576
- >
577
- JWT Sessions
578
- </TabButton>
579
- <TabButton
580
- $active={activeTab === 'ip-bans'}
581
- onClick={() => setActiveTab('ip-bans')}
582
- startIcon={<Earth />}
583
- size="L"
584
- >
585
- IP-Sperren
586
- </TabButton>
587
- </TabContainer>
588
-
589
- {/* Statistik Cards */}
590
- <StatsGrid gap={4}>
591
- {statCards.map((stat, index) => {
592
- const IconComponent = stat.icon;
593
- return (
594
- <Grid.Item col={3} key={index}>
595
- <StatsCard
596
- $gradientColor={stat.bgColor}
597
- $delay={stat.delay}
598
- >
599
- <Flex direction="column" alignItems="center">
600
- <IconWrapper
601
- className="icon-wrapper"
602
- $bgColor={stat.bgColor}
603
- $iconColor={stat.iconColor}
604
- >
605
- <IconComponent />
606
- </IconWrapper>
607
- <StatValue className="stat-value">
608
- {stat.value}
609
- </StatValue>
610
- <Typography variant="epsilon" textColor="neutral600">
611
- {stat.title}
612
- </Typography>
613
- </Flex>
614
- </StatsCard>
615
- </Grid.Item>
616
- );
617
- })}
618
- </StatsGrid>
619
-
620
- {/* Tabellen Bereich */}
621
- {activeTab === 'magic-links' && (
622
- <Box>
623
- {tokens.length === 0 ? (
624
- <EmptyStateWrapper>
625
- <EmptyStateLayout
626
- icon={<Eye style={{ width: '64px', height: '64px', color: '#667eea' }} />}
627
- content={
628
- <Box>
629
- <Typography variant="alpha" style={{ marginBottom: '12px' }}>
630
- Keine Magic Link Tokens vorhanden
631
- </Typography>
632
- <Typography variant="omega" textColor="neutral600">
633
- Erstelle dein erstes Token oder aktualisiere die Ansicht
634
- </Typography>
635
- </Box>
636
- }
637
- action={
638
- <Flex gap={3} justifyContent="center">
639
- <Button
640
- onClick={() => setShowCreateModal(true)}
641
- startIcon={<Plus />}
642
- size="L"
643
- style={{
644
- background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
645
- color: 'white',
646
- border: 'none',
647
- fontWeight: '600',
648
- }}
649
- >
650
- Erstes Token erstellen
651
- </Button>
652
- <Button
653
- onClick={handleRefresh}
654
- variant="secondary"
655
- size="L"
656
- >
657
- Aktualisieren
658
- </Button>
659
- </Flex>
660
- }
661
- />
662
- </EmptyStateWrapper>
663
- ) : (
664
- <StyledTable>
665
- <Thead>
666
- <Tr>
667
- <Th>
668
- <Checkbox />
669
- </Th>
670
- <Th>Email</Th>
671
- <Th>Token</Th>
672
- <Th>Status</Th>
673
- <Th>Erstellt</Th>
674
- <Th>Gültig bis</Th>
675
- <Th>Aktionen</Th>
676
- </Tr>
677
- </Thead>
678
- <Tbody>
679
- {tokens.map((token) => (
680
- <Tr key={token.id}>
681
- <Td>
682
- <Checkbox
683
- value={selectedTokens.includes(token.id)}
684
- onCheckedChange={(value) => {
685
- if (value) {
686
- setSelectedTokens([...selectedTokens, token.id]);
687
- } else {
688
- setSelectedTokens(selectedTokens.filter(id => id !== token.id));
689
- }
690
- }}
691
- />
692
- </Td>
693
- <Td>
694
- <Flex alignItems="center" gap={2}>
695
- <Mail style={{ color: '#667eea' }} />
696
- <Typography fontWeight="semiBold">
697
- {token.email}
698
- </Typography>
699
- </Flex>
700
- </Td>
701
- <Td>
702
- <Typography variant="pi" textColor="neutral600">
703
- {token.login_token?.substring(0, 20)}...
704
- </Typography>
705
- </Td>
706
- <Td>
707
- {token.used ? (
708
- <AnimatedBadge variant="secondary">
709
- Verwendet
710
- </AnimatedBadge>
711
- ) : token.expired ? (
712
- <AnimatedBadge variant="warning">
713
- Abgelaufen
714
- </AnimatedBadge>
715
- ) : (
716
- <AnimatedBadge variant="success">
717
- Aktiv
718
- </AnimatedBadge>
719
- )}
720
- </Td>
721
- <Td>
722
- <Flex alignItems="center" gap={1}>
723
- <Calendar style={{ width: '16px', height: '16px', color: '#8b5cf6' }} />
724
- <Typography variant="pi">
725
- {new Date(token.created_at).toLocaleDateString('de-DE')}
726
- </Typography>
727
- </Flex>
728
- </Td>
729
- <Td>
730
- <Flex alignItems="center" gap={1}>
731
- <Clock style={{ width: '16px', height: '16px', color: '#f59e0b' }} />
732
- <Typography variant="pi">
733
- {new Date(token.expires_at).toLocaleDateString('de-DE')}
734
- </Typography>
735
- </Flex>
736
- </Td>
737
- <Td>
738
- <Flex gap={2}>
739
- <Tooltip content="Details">
740
- <ActionIconButton
741
- icon={<Eye />}
742
- variant="ghost"
743
- />
744
- </Tooltip>
745
- <Tooltip content="Bearbeiten">
746
- <ActionIconButton
747
- icon={<Pencil />}
748
- variant="ghost"
749
- />
750
- </Tooltip>
751
- <Tooltip content="Löschen">
752
- <ActionIconButton
753
- onClick={() => handleDelete(token.id)}
754
- icon={<Trash />}
755
- variant="ghost"
756
- />
757
- </Tooltip>
758
- </Flex>
759
- </Td>
760
- </Tr>
761
- ))}
762
- </Tbody>
763
- </StyledTable>
764
- )}
765
- </Box>
766
- )}
767
-
768
- {/* Andere Tabs */}
769
- {activeTab === 'jwt-sessions' && (
770
- <EmptyStateWrapper>
771
- <Typography variant="alpha">JWT Sessions</Typography>
772
- <Typography variant="omega" textColor="neutral600" style={{ marginTop: '12px' }}>
773
- Kommt bald... 🚀
774
- </Typography>
775
- </EmptyStateWrapper>
776
- )}
777
-
778
- {activeTab === 'ip-bans' && (
779
- <EmptyStateWrapper>
780
- <Typography variant="alpha">IP-Sperren</Typography>
781
- <Typography variant="omega" textColor="neutral600" style={{ marginTop: '12px' }}>
782
- Kommt bald... 🔒
783
- </Typography>
784
- </EmptyStateWrapper>
785
- )}
786
-
787
- {/* Create Modal */}
788
- <CreateTokenModal
789
- isOpen={showCreateModal}
790
- onClose={() => {
791
- setShowCreateModal(false);
792
- setCreateFormData({
793
- email: '',
794
- ttl: 24,
795
- sendEmail: true
796
- });
797
- }}
798
- onSubmit={handleCreateToken}
799
- formData={createFormData}
800
- setFormData={setCreateFormData}
801
- />
802
- </PageWrapper>
803
- </Main>
804
- );
805
- };
806
-
807
- export default TokensRedesign;