@hed-hog/core 0.0.215 → 0.0.216

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 (36) hide show
  1. package/hedhog/frontend/app/dashboard/[slug]/dashboard-content.tsx.ejs +5 -5
  2. package/hedhog/frontend/app/dashboard/[slug]/widget-renderer.tsx.ejs +1 -1
  3. package/hedhog/frontend/app/dashboard/components/add-widget-selector-dialog.tsx.ejs +312 -0
  4. package/hedhog/frontend/app/dashboard/components/dashboard-grid.tsx.ejs +54 -0
  5. package/hedhog/frontend/app/dashboard/components/draggable-grid.tsx.ejs +132 -0
  6. package/hedhog/frontend/app/dashboard/components/dynamic-widget.tsx.ejs +88 -0
  7. package/hedhog/frontend/app/dashboard/components/index.ts.ejs +6 -0
  8. package/hedhog/frontend/app/dashboard/components/stats.tsx.ejs +93 -0
  9. package/hedhog/frontend/app/dashboard/components/widget-wrapper.tsx.ejs +150 -0
  10. package/hedhog/frontend/app/dashboard/components/widgets/account-security.tsx.ejs +184 -0
  11. package/hedhog/frontend/app/dashboard/components/widgets/active-users-card.tsx.ejs +58 -0
  12. package/hedhog/frontend/app/dashboard/components/widgets/activity-timeline.tsx.ejs +219 -0
  13. package/hedhog/frontend/app/dashboard/components/widgets/email-notifications.tsx.ejs +191 -0
  14. package/hedhog/frontend/app/dashboard/components/widgets/locale-config.tsx.ejs +309 -0
  15. package/hedhog/frontend/app/dashboard/components/widgets/login-history-chart.tsx.ejs +111 -0
  16. package/hedhog/frontend/app/dashboard/components/widgets/mail-config.tsx.ejs +445 -0
  17. package/hedhog/frontend/app/dashboard/components/widgets/mail-sent-card.tsx.ejs +58 -0
  18. package/hedhog/frontend/app/dashboard/components/widgets/mail-sent-chart.tsx.ejs +149 -0
  19. package/hedhog/frontend/app/dashboard/components/widgets/oauth-config.tsx.ejs +296 -0
  20. package/hedhog/frontend/app/dashboard/components/widgets/permissions-card.tsx.ejs +61 -0
  21. package/hedhog/frontend/app/dashboard/components/widgets/permissions-chart.tsx.ejs +152 -0
  22. package/hedhog/frontend/app/dashboard/components/widgets/profile-card.tsx.ejs +186 -0
  23. package/hedhog/frontend/app/dashboard/components/widgets/session-activity-chart.tsx.ejs +183 -0
  24. package/hedhog/frontend/app/dashboard/components/widgets/sessions-today-card.tsx.ejs +62 -0
  25. package/hedhog/frontend/app/dashboard/components/widgets/stat-access-level.tsx.ejs +57 -0
  26. package/hedhog/frontend/app/dashboard/components/widgets/stat-actions-today.tsx.ejs +57 -0
  27. package/hedhog/frontend/app/dashboard/components/widgets/stat-consecutive-days.tsx.ejs +57 -0
  28. package/hedhog/frontend/app/dashboard/components/widgets/stat-online-time.tsx.ejs +57 -0
  29. package/hedhog/frontend/app/dashboard/components/widgets/storage-config.tsx.ejs +340 -0
  30. package/hedhog/frontend/app/dashboard/components/widgets/theme-config.tsx.ejs +275 -0
  31. package/hedhog/frontend/app/dashboard/components/widgets/user-growth-chart.tsx.ejs +210 -0
  32. package/hedhog/frontend/app/dashboard/components/widgets/user-roles.tsx.ejs +130 -0
  33. package/hedhog/frontend/app/dashboard/components/widgets/user-sessions.tsx.ejs +233 -0
  34. package/hedhog/frontend/messages/en.json +143 -1
  35. package/hedhog/frontend/messages/pt.json +143 -1
  36. package/package.json +3 -3
@@ -0,0 +1,210 @@
1
+ 'use client';
2
+
3
+ import {
4
+ Card,
5
+ CardContent,
6
+ CardDescription,
7
+ CardHeader,
8
+ CardTitle,
9
+ } from '@/components/ui/card';
10
+ import { useWidgetData } from '@/hooks/use-widget-data';
11
+ import { IconGripVertical } from '@tabler/icons-react';
12
+ import { useTranslations } from 'next-intl';
13
+ import {
14
+ Area,
15
+ AreaChart,
16
+ CartesianGrid,
17
+ ResponsiveContainer,
18
+ Tooltip,
19
+ XAxis,
20
+ YAxis,
21
+ } from 'recharts';
22
+ import { WidgetWrapper } from '../widget-wrapper';
23
+
24
+ function CustomTooltip({
25
+ active,
26
+ payload,
27
+ label,
28
+ }: {
29
+ active?: boolean;
30
+ payload?: Array<{ value: number; dataKey: string }>;
31
+ label?: string;
32
+ }) {
33
+ const t = useTranslations('core.Dashboard');
34
+ if (!active || !payload?.length) return null;
35
+
36
+ return (
37
+ <div className="rounded-lg border bg-card px-3 py-2 shadow-xl">
38
+ <p className="mb-1 text-xs font-medium text-card-foreground">{label}</p>
39
+ {payload.map((entry) => (
40
+ <p key={entry.dataKey} className="text-xs text-muted-foreground">
41
+ <span
42
+ className="mr-1.5 inline-block h-2 w-2 rounded-full"
43
+ style={{
44
+ backgroundColor:
45
+ entry.dataKey === 'users' ? '#6366f1' : '#10b981',
46
+ }}
47
+ />
48
+ {entry.dataKey === 'users' ? t('users') : t('sessions')}:{' '}
49
+ {entry.value.toLocaleString('pt-BR')}
50
+ </p>
51
+ ))}
52
+ </div>
53
+ );
54
+ }
55
+
56
+ interface UserGrowthChartProps {
57
+ widget?: any;
58
+ onRemove?: () => void;
59
+ }
60
+
61
+ interface UserStatsData {
62
+ charts?: {
63
+ userGrowth?: Array<{
64
+ month: string;
65
+ users: number;
66
+ sessions: number;
67
+ }>;
68
+ };
69
+ }
70
+
71
+ export default function UserGrowthChart({
72
+ widget,
73
+ onRemove,
74
+ }: UserGrowthChartProps) {
75
+ const t = useTranslations('core.Dashboard');
76
+
77
+ const {
78
+ data: statsData,
79
+ isLoading,
80
+ isAccessDenied,
81
+ isError,
82
+ } = useWidgetData<UserStatsData>({
83
+ endpoint: '/dashboard-core/stats/overview/users',
84
+ queryKey: 'dashboard-stats-users',
85
+ });
86
+
87
+ const data = statsData?.charts?.userGrowth || [];
88
+
89
+ return (
90
+ <WidgetWrapper
91
+ isLoading={isLoading}
92
+ isAccessDenied={isAccessDenied}
93
+ isError={isError}
94
+ widgetName={widget?.name || t('userGrowthTitle')}
95
+ onRemove={onRemove}
96
+ >
97
+ <Card className="h-full flex flex-col group">
98
+ <div
99
+ className="drag-handle absolute top-3 left-4 z-10"
100
+ style={{ cursor: 'grab' }}
101
+ >
102
+ <IconGripVertical className="text-muted-foreground/50 size-4 shrink-0" />
103
+ </div>
104
+ <CardHeader className="flex flex-row items-center justify-between pb-2 pt-4">
105
+ <div className="pl-6">
106
+ <CardTitle className="text-base font-semibold">
107
+ {t('userGrowthTitle')}
108
+ </CardTitle>
109
+ <CardDescription>{t('userGrowthDescription')}</CardDescription>
110
+ </div>
111
+ </CardHeader>
112
+ <CardContent className="flex-1 pt-0">
113
+ <div className="h-[280px] w-full">
114
+ <ResponsiveContainer width="100%" height="100%">
115
+ <AreaChart
116
+ data={data}
117
+ margin={{ top: 5, right: 10, left: -20, bottom: 0 }}
118
+ >
119
+ <defs>
120
+ <linearGradient
121
+ id="gradientUsuarios"
122
+ x1="0"
123
+ y1="0"
124
+ x2="0"
125
+ y2="1"
126
+ >
127
+ <stop offset="0%" stopColor="#6366f1" stopOpacity={0.5} />
128
+ <stop
129
+ offset="100%"
130
+ stopColor="#6366f1"
131
+ stopOpacity={0.05}
132
+ />
133
+ </linearGradient>
134
+ <linearGradient
135
+ id="gradientSessoes"
136
+ x1="0"
137
+ y1="0"
138
+ x2="0"
139
+ y2="1"
140
+ >
141
+ <stop offset="0%" stopColor="#10b981" stopOpacity={0.4} />
142
+ <stop
143
+ offset="100%"
144
+ stopColor="#10b981"
145
+ stopOpacity={0.05}
146
+ />
147
+ </linearGradient>
148
+ </defs>
149
+ <CartesianGrid
150
+ strokeDasharray="3 3"
151
+ stroke="currentColor"
152
+ opacity={0.1}
153
+ />
154
+ <XAxis
155
+ dataKey="month"
156
+ tick={{ fill: 'hsl(var(--muted-foreground))', fontSize: 12 }}
157
+ axisLine={false}
158
+ tickLine={false}
159
+ />
160
+ <YAxis
161
+ tick={{ fill: 'hsl(var(--muted-foreground))', fontSize: 12 }}
162
+ axisLine={false}
163
+ tickLine={false}
164
+ />
165
+ <Tooltip content={<CustomTooltip />} />
166
+ <Area
167
+ type="monotone"
168
+ dataKey="sessions"
169
+ stroke="#10b981"
170
+ strokeWidth={2.5}
171
+ fill="url(#gradientSessoes)"
172
+ animationDuration={1500}
173
+ />
174
+ <Area
175
+ type="monotone"
176
+ dataKey="users"
177
+ stroke="#6366f1"
178
+ strokeWidth={2.5}
179
+ fill="url(#gradientUsuarios)"
180
+ animationDuration={1500}
181
+ animationBegin={200}
182
+ />
183
+ </AreaChart>
184
+ </ResponsiveContainer>
185
+ </div>
186
+ <div className="mt-3 flex items-center justify-center gap-6">
187
+ <div className="flex items-center gap-2">
188
+ <div
189
+ className="h-2.5 w-2.5 rounded-full"
190
+ style={{ backgroundColor: '#6366f1' }}
191
+ />
192
+ <span className="text-xs text-muted-foreground">
193
+ {t('users')}
194
+ </span>
195
+ </div>
196
+ <div className="flex items-center gap-2">
197
+ <div
198
+ className="h-2.5 w-2.5 rounded-full"
199
+ style={{ backgroundColor: '#10b981' }}
200
+ />
201
+ <span className="text-xs text-muted-foreground">
202
+ {t('sessions')}
203
+ </span>
204
+ </div>
205
+ </div>
206
+ </CardContent>
207
+ </Card>
208
+ </WidgetWrapper>
209
+ );
210
+ }
@@ -0,0 +1,130 @@
1
+ 'use client';
2
+
3
+ import { Badge } from '@/components/ui/badge';
4
+ import {
5
+ Card,
6
+ CardContent,
7
+ CardDescription,
8
+ CardHeader,
9
+ CardTitle,
10
+ } from '@/components/ui/card';
11
+ import { useWidgetData } from '@/hooks/use-widget-data';
12
+ import type { AllWidgetsData, RoleData } from '@/types/widget-data';
13
+ import { Crown, ShieldCheck } from 'lucide-react';
14
+ import { useTranslations } from 'next-intl';
15
+ import { WidgetWrapper } from '../widget-wrapper';
16
+
17
+ const levelStyles = [
18
+ {
19
+ border: 'border-blue-200 dark:border-blue-800',
20
+ bg: 'bg-blue-50/50 dark:bg-blue-950/30',
21
+ iconBg: 'bg-blue-100 dark:bg-blue-900/50',
22
+ iconColor: 'text-blue-600 dark:text-blue-400',
23
+ badge: 'bg-blue-50 text-blue-700 dark:bg-blue-950/40 dark:text-blue-400',
24
+ },
25
+ {
26
+ border: 'border-indigo-200 dark:border-indigo-800',
27
+ bg: 'bg-indigo-50/30 dark:bg-indigo-950/20',
28
+ iconBg: 'bg-indigo-100 dark:bg-indigo-900/50',
29
+ iconColor: 'text-indigo-600 dark:text-indigo-400',
30
+ badge:
31
+ 'bg-indigo-50 text-indigo-700 dark:bg-indigo-950/40 dark:text-indigo-400',
32
+ },
33
+ {
34
+ border: 'border-emerald-200 dark:border-emerald-800',
35
+ bg: 'bg-emerald-50/30 dark:bg-emerald-950/20',
36
+ iconBg: 'bg-emerald-100 dark:bg-emerald-900/50',
37
+ iconColor: 'text-emerald-600 dark:text-emerald-400',
38
+ badge:
39
+ 'bg-emerald-50 text-emerald-700 dark:bg-emerald-950/40 dark:text-emerald-400',
40
+ },
41
+ {
42
+ border: 'border-amber-200 dark:border-amber-800',
43
+ bg: 'bg-amber-50/30 dark:bg-amber-950/20',
44
+ iconBg: 'bg-amber-100 dark:bg-amber-900/50',
45
+ iconColor: 'text-amber-600 dark:text-amber-400',
46
+ badge:
47
+ 'bg-amber-50 text-amber-700 dark:bg-amber-950/40 dark:text-amber-400',
48
+ },
49
+ ];
50
+
51
+ function RolesContent({ roles }: { roles: RoleData[] }) {
52
+ const t = useTranslations('core.DashboardPage.userRoles');
53
+
54
+ return (
55
+ <Card className="flex h-full flex-col">
56
+ <CardHeader className="shrink-0 pb-3">
57
+ <div className="flex items-center gap-2">
58
+ <Crown className="h-5 w-5 text-amber-600 dark:text-amber-400" />
59
+ <div>
60
+ <CardTitle className="text-base font-semibold">
61
+ {t('title')}
62
+ </CardTitle>
63
+ <CardDescription>{t('description')}</CardDescription>
64
+ </div>
65
+ </div>
66
+ </CardHeader>
67
+ <CardContent className="flex-1 overflow-auto pt-0">
68
+ <div className="grid grid-cols-2 gap-2">
69
+ {roles.map((role, index) => {
70
+ const style = levelStyles[index % levelStyles.length]!;
71
+ return (
72
+ <div
73
+ key={role.id}
74
+ className={`flex items-center gap-3 rounded-xl border p-3 transition-all duration-200 hover:shadow-sm ${style.border} ${style.bg}`}
75
+ >
76
+ <div
77
+ className={`flex h-9 w-9 shrink-0 items-center justify-center rounded-lg ${style.iconBg}`}
78
+ >
79
+ <ShieldCheck className={`h-4 w-4 ${style.iconColor}`} />
80
+ </div>
81
+ <div className="flex min-w-0 flex-1 flex-col gap-0.5">
82
+ <span className="text-sm font-medium text-foreground">
83
+ {role.name}
84
+ </span>
85
+ <span className="text-xs text-muted-foreground">
86
+ {role.slug}
87
+ </span>
88
+ </div>
89
+ <Badge
90
+ variant="secondary"
91
+ className={`shrink-0 text-[10px] ${style.badge}`}
92
+ >
93
+ {t('active')}
94
+ </Badge>
95
+ </div>
96
+ );
97
+ })}
98
+ </div>
99
+ </CardContent>
100
+ </Card>
101
+ );
102
+ }
103
+
104
+ interface UserRolesProps {
105
+ widget?: { name?: string };
106
+ onRemove?: () => void;
107
+ }
108
+
109
+ export default function UserRoles({ widget, onRemove }: UserRolesProps) {
110
+ const { data, isLoading, isError, isAccessDenied } = useWidgetData<
111
+ AllWidgetsData,
112
+ RoleData[]
113
+ >({
114
+ endpoint: '/dashboard-core/widgets/me',
115
+ queryKey: 'widget-me',
116
+ select: (d) => d.userRoles,
117
+ });
118
+
119
+ return (
120
+ <WidgetWrapper
121
+ isLoading={isLoading}
122
+ isError={isError}
123
+ isAccessDenied={isAccessDenied}
124
+ widgetName={widget?.name ?? 'user-roles'}
125
+ onRemove={onRemove}
126
+ >
127
+ {data && <RolesContent roles={data} />}
128
+ </WidgetWrapper>
129
+ );
130
+ }
@@ -0,0 +1,233 @@
1
+ 'use client';
2
+
3
+ import { Badge } from '@/components/ui/badge';
4
+ import { Button } from '@/components/ui/button';
5
+ import {
6
+ Card,
7
+ CardContent,
8
+ CardDescription,
9
+ CardHeader,
10
+ CardTitle,
11
+ } from '@/components/ui/card';
12
+ import {
13
+ DropdownMenu,
14
+ DropdownMenuContent,
15
+ DropdownMenuItem,
16
+ DropdownMenuTrigger,
17
+ } from '@/components/ui/dropdown-menu';
18
+ import { useWidgetData } from '@/hooks/use-widget-data';
19
+ import type { AllWidgetsData, SessionData } from '@/types/widget-data';
20
+ import {
21
+ Clock,
22
+ Globe,
23
+ Info,
24
+ LogOut,
25
+ Monitor,
26
+ MoreHorizontal,
27
+ Smartphone,
28
+ Tablet,
29
+ } from 'lucide-react';
30
+ import { useTranslations } from 'next-intl';
31
+ import { useRouter } from 'next/navigation';
32
+ import { WidgetWrapper } from '../widget-wrapper';
33
+
34
+ function detectDeviceType(ua: string): 'desktop' | 'mobile' | 'tablet' {
35
+ const u = ua.toLowerCase();
36
+ if (u.includes('ipad') || u.includes('tablet')) return 'tablet';
37
+ if (u.includes('mobile') || u.includes('iphone') || u.includes('android'))
38
+ return 'mobile';
39
+ return 'desktop';
40
+ }
41
+
42
+ function detectBrowser(ua: string): string {
43
+ if (/edg\//i.test(ua)) return 'Edge';
44
+ if (/chrome\//i.test(ua)) return 'Chrome';
45
+ if (/firefox\//i.test(ua)) return 'Firefox';
46
+ if (/safari\//i.test(ua)) return 'Safari';
47
+ if (/msie|trident/i.test(ua)) return 'IE';
48
+ return 'Browser';
49
+ }
50
+
51
+ function detectDevice(ua: string): string {
52
+ if (/ipad/i.test(ua)) return 'iPad';
53
+ if (/iphone/i.test(ua)) return 'iPhone';
54
+ if (/android/i.test(ua)) return 'Android';
55
+ if (/macintosh|mac os/i.test(ua)) return 'Mac';
56
+ if (/windows/i.test(ua)) return 'Windows';
57
+ if (/linux/i.test(ua)) return 'Linux';
58
+ return 'Dispositivo';
59
+ }
60
+
61
+ function formatRelative(
62
+ dateStr: string,
63
+ tActiveNow: string,
64
+ tMinsAgo: (n: number) => string,
65
+ tHoursAgo: (n: number) => string,
66
+ tDaysAgo: (n: number) => string
67
+ ): string {
68
+ const diff = Date.now() - new Date(dateStr).getTime();
69
+ const mins = Math.floor(diff / 60000);
70
+ if (mins < 1) return tActiveNow;
71
+ if (mins < 60) return tMinsAgo(mins);
72
+ const hours = Math.floor(mins / 60);
73
+ if (hours < 24) return tHoursAgo(hours);
74
+ const days = Math.floor(hours / 24);
75
+ return tDaysAgo(days);
76
+ }
77
+
78
+ const deviceIcons = {
79
+ desktop: Monitor,
80
+ mobile: Smartphone,
81
+ tablet: Tablet,
82
+ };
83
+
84
+ function SessionsContent({ sessions }: { sessions: SessionData[] }) {
85
+ const t = useTranslations('core.DashboardPage.userSessions');
86
+ const router = useRouter();
87
+
88
+ return (
89
+ <Card className="flex h-full flex-col">
90
+ <CardHeader className="shrink-0 pb-3">
91
+ <div className="flex items-center justify-between">
92
+ <div className="flex items-center gap-2">
93
+ <Globe className="h-5 w-5 text-blue-600" />
94
+ <div>
95
+ <CardTitle className="text-base font-semibold">
96
+ {t('title')}
97
+ </CardTitle>
98
+ <CardDescription>{t('description')}</CardDescription>
99
+ </div>
100
+ </div>
101
+ <Button
102
+ variant="outline"
103
+ size="sm"
104
+ onClick={() => router.push('/core/account/sessions')}
105
+ >
106
+ <Info className="h-3.5 w-3.5" />
107
+ {t('moreInfo')}
108
+ </Button>
109
+ </div>
110
+ </CardHeader>
111
+ <CardContent className="flex-1 overflow-auto pt-0">
112
+ <div className="flex flex-col gap-2">
113
+ {sessions.map((session, index) => {
114
+ const ua = session.user_agent ?? '';
115
+ const deviceType = detectDeviceType(ua);
116
+ const browser = detectBrowser(ua);
117
+ const device = detectDevice(ua);
118
+ const isCurrent = index === 0;
119
+ const DeviceIcon = deviceIcons[deviceType];
120
+ const ip = session.ip_address
121
+ ? session.ip_address.replace(/(\d+\.\d+\.\d+\.)\d+/, '$1***')
122
+ : '—';
123
+ const relativeTime = formatRelative(
124
+ session.created_at,
125
+ t('activeNow'),
126
+ (n) => t('minutesAgo', { count: n }),
127
+ (n) => t('hoursAgo', { count: n }),
128
+ (n) =>
129
+ n === 1
130
+ ? t('daysAgo', { count: n })
131
+ : t('daysAgoPlural', { count: n })
132
+ );
133
+
134
+ return (
135
+ <div
136
+ key={session.id}
137
+ className={`group flex items-center gap-3 rounded-xl border p-3.5 transition-all duration-200 hover:shadow-sm ${
138
+ isCurrent
139
+ ? 'border-emerald-200 bg-emerald-50/50 dark:border-emerald-800 dark:bg-emerald-950/30'
140
+ : 'bg-card hover:bg-muted/30'
141
+ }`}
142
+ >
143
+ <div
144
+ className={`flex h-10 w-10 shrink-0 items-center justify-center rounded-lg ${
145
+ isCurrent
146
+ ? 'bg-emerald-100 dark:bg-emerald-900/50'
147
+ : 'bg-muted'
148
+ }`}
149
+ >
150
+ <DeviceIcon
151
+ className={`h-5 w-5 ${
152
+ isCurrent
153
+ ? 'text-emerald-600 dark:text-emerald-400'
154
+ : 'text-muted-foreground'
155
+ }`}
156
+ />
157
+ </div>
158
+ <div className="flex min-w-0 flex-1 flex-col gap-0.5">
159
+ <div className="flex items-center gap-2">
160
+ <span className="text-sm font-medium text-foreground">
161
+ {device}
162
+ </span>
163
+ {isCurrent && (
164
+ <Badge className="bg-emerald-100 text-[10px] text-emerald-700 hover:bg-emerald-100 dark:bg-emerald-900/50 dark:text-emerald-400 dark:hover:bg-emerald-900/50">
165
+ {t('thisSession')}
166
+ </Badge>
167
+ )}
168
+ </div>
169
+ <span className="text-xs text-muted-foreground">
170
+ {browser} &middot; {ip}
171
+ </span>
172
+ <div className="flex items-center gap-3 text-[11px] text-muted-foreground/70">
173
+ <span className="flex items-center gap-1">
174
+ <Clock className="h-3 w-3" />
175
+ {relativeTime}
176
+ </span>
177
+ </div>
178
+ </div>
179
+ {!isCurrent && (
180
+ <DropdownMenu>
181
+ <DropdownMenuTrigger asChild>
182
+ <Button
183
+ variant="ghost"
184
+ size="icon"
185
+ className="h-8 w-8 shrink-0 opacity-0 transition-opacity group-hover:opacity-100"
186
+ >
187
+ <MoreHorizontal className="h-4 w-4" />
188
+ </Button>
189
+ </DropdownMenuTrigger>
190
+ <DropdownMenuContent align="end">
191
+ <DropdownMenuItem className="text-destructive">
192
+ <LogOut className="mr-2 h-4 w-4" />
193
+ {t('terminateSession')}
194
+ </DropdownMenuItem>
195
+ </DropdownMenuContent>
196
+ </DropdownMenu>
197
+ )}
198
+ </div>
199
+ );
200
+ })}
201
+ </div>
202
+ </CardContent>
203
+ </Card>
204
+ );
205
+ }
206
+
207
+ interface UserSessionsProps {
208
+ widget?: { name?: string };
209
+ onRemove?: () => void;
210
+ }
211
+
212
+ export default function UserSessions({ widget, onRemove }: UserSessionsProps) {
213
+ const { data, isLoading, isError, isAccessDenied } = useWidgetData<
214
+ AllWidgetsData,
215
+ SessionData[]
216
+ >({
217
+ endpoint: '/dashboard-core/widgets/me',
218
+ queryKey: 'widget-me',
219
+ select: (d) => d.userSessions,
220
+ });
221
+
222
+ return (
223
+ <WidgetWrapper
224
+ isLoading={isLoading}
225
+ isError={isError}
226
+ isAccessDenied={isAccessDenied}
227
+ widgetName={widget?.name ?? 'user-sessions'}
228
+ onRemove={onRemove}
229
+ >
230
+ {data && <SessionsContent sessions={data} />}
231
+ </WidgetWrapper>
232
+ );
233
+ }