@hed-hog/core 0.0.276 → 0.0.279
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 +60 -0
- package/dist/auth/auth.controller.d.ts +8 -1
- package/dist/auth/auth.controller.d.ts.map +1 -1
- package/dist/auth/auth.controller.js +7 -7
- package/dist/auth/auth.controller.js.map +1 -1
- package/dist/auth/auth.service.d.ts +10 -1
- package/dist/auth/auth.service.d.ts.map +1 -1
- package/dist/auth/auth.service.js +34 -8
- package/dist/auth/auth.service.js.map +1 -1
- package/dist/dashboard/dashboard-core/dashboard-core.controller.d.ts +12 -0
- package/dist/dashboard/dashboard-core/dashboard-core.controller.d.ts.map +1 -1
- package/dist/dashboard/dashboard-core/dashboard-core.controller.js +9 -0
- package/dist/dashboard/dashboard-core/dashboard-core.controller.js.map +1 -1
- package/dist/dashboard/dashboard-core/dashboard-core.service.d.ts +12 -0
- package/dist/dashboard/dashboard-core/dashboard-core.service.d.ts.map +1 -1
- package/dist/dashboard/dashboard-core/dashboard-core.service.js +25 -0
- package/dist/dashboard/dashboard-core/dashboard-core.service.js.map +1 -1
- package/dist/profile/profile.service.js +1 -1
- package/dist/profile/profile.service.js.map +1 -1
- package/dist/role/guards/role.guard.d.ts +1 -0
- package/dist/role/guards/role.guard.d.ts.map +1 -1
- package/dist/role/guards/role.guard.js +18 -0
- package/dist/role/guards/role.guard.js.map +1 -1
- package/dist/session/session.service.js +1 -1
- package/dist/session/session.service.js.map +1 -1
- package/dist/user/dto/reset-password.dto.d.ts +4 -0
- package/dist/user/dto/reset-password.dto.d.ts.map +1 -0
- package/dist/user/dto/reset-password.dto.js +26 -0
- package/dist/user/dto/reset-password.dto.js.map +1 -0
- package/dist/user/user.controller.d.ts +5 -0
- package/dist/user/user.controller.d.ts.map +1 -1
- package/dist/user/user.controller.js +13 -0
- package/dist/user/user.controller.js.map +1 -1
- package/dist/user/user.service.d.ts +6 -0
- package/dist/user/user.service.d.ts.map +1 -1
- package/dist/user/user.service.js +65 -0
- package/dist/user/user.service.js.map +1 -1
- package/hedhog/data/dashboard_component.yaml +74 -12
- package/hedhog/data/dashboard_component_role.yaml +223 -145
- package/hedhog/data/dashboard_item.yaml +42 -22
- package/hedhog/data/dashboard_role.yaml +18 -12
- package/hedhog/data/menu.yaml +6 -0
- package/hedhog/data/route.yaml +65 -1
- package/hedhog/frontend/app/account/components/change-password-form.tsx.ejs +2 -1
- package/hedhog/frontend/app/ai_agent/page.tsx.ejs +17 -17
- package/hedhog/frontend/app/dashboard/[slug]/dashboard-content.tsx.ejs +23 -12
- package/hedhog/frontend/app/dashboard/components/draggable-grid.tsx.ejs +80 -5
- package/hedhog/frontend/app/dashboard/components/widgets/account-security.tsx.ejs +17 -13
- package/hedhog/frontend/app/dashboard/components/widgets/activity-timeline.tsx.ejs +16 -12
- package/hedhog/frontend/app/dashboard/components/widgets/email-notifications.tsx.ejs +27 -16
- package/hedhog/frontend/app/dashboard/components/widgets/login-history-chart.tsx.ejs +13 -9
- package/hedhog/frontend/app/dashboard/components/widgets/menus-card.tsx.ejs +58 -0
- package/hedhog/frontend/app/dashboard/components/widgets/permissions-chart.tsx.ejs +62 -58
- package/hedhog/frontend/app/dashboard/components/widgets/routes-card.tsx.ejs +58 -0
- package/hedhog/frontend/app/dashboard/components/widgets/stat-access-level.tsx.ejs +6 -6
- package/hedhog/frontend/app/dashboard/components/widgets/stat-actions-today.tsx.ejs +6 -6
- package/hedhog/frontend/app/dashboard/components/widgets/stat-consecutive-days.tsx.ejs +6 -6
- package/hedhog/frontend/app/dashboard/components/widgets/stat-online-time.tsx.ejs +6 -6
- package/hedhog/frontend/app/dashboard/components/widgets/user-roles.tsx.ejs +15 -11
- package/hedhog/frontend/app/dashboard/components/widgets/user-sessions.tsx.ejs +18 -15
- package/hedhog/frontend/app/dashboard/dashboard.css.ejs +20 -4
- package/hedhog/frontend/app/dashboard/page.tsx.ejs +29 -14
- package/hedhog/frontend/app/mail/log/page.tsx.ejs +5 -11
- package/hedhog/frontend/app/users/page.tsx.ejs +331 -10
- package/hedhog/frontend/messages/en.json +29 -3
- package/hedhog/frontend/messages/pt.json +29 -3
- package/package.json +4 -4
- package/src/auth/auth.controller.ts +21 -20
- package/src/auth/auth.service.ts +63 -15
- package/src/dashboard/dashboard-core/dashboard-core.controller.ts +5 -0
- package/src/dashboard/dashboard-core/dashboard-core.service.ts +34 -0
- package/src/profile/profile.service.ts +1 -1
- package/src/role/guards/role.guard.ts +36 -7
- package/src/session/session.service.ts +2 -2
- package/src/user/dto/reset-password.dto.ts +11 -0
- package/src/user/user.controller.ts +24 -14
- package/src/user/user.service.ts +84 -0
package/hedhog/data/route.yaml
CHANGED
|
@@ -28,6 +28,22 @@
|
|
|
28
28
|
slug: admin
|
|
29
29
|
- where:
|
|
30
30
|
slug: admin-access
|
|
31
|
+
- url: /auth/refresh
|
|
32
|
+
method: POST
|
|
33
|
+
relations:
|
|
34
|
+
role:
|
|
35
|
+
- where:
|
|
36
|
+
slug: admin
|
|
37
|
+
- where:
|
|
38
|
+
slug: admin-access
|
|
39
|
+
- url: /auth/logout
|
|
40
|
+
method: POST
|
|
41
|
+
relations:
|
|
42
|
+
role:
|
|
43
|
+
- where:
|
|
44
|
+
slug: admin
|
|
45
|
+
- where:
|
|
46
|
+
slug: admin-access
|
|
31
47
|
- url: /sessions/revoke-all
|
|
32
48
|
method: DELETE
|
|
33
49
|
relations:
|
|
@@ -344,60 +360,86 @@
|
|
|
344
360
|
role:
|
|
345
361
|
- where:
|
|
346
362
|
slug: admin
|
|
363
|
+
- where:
|
|
364
|
+
slug: admin-user
|
|
347
365
|
- url: /user/:userId/role
|
|
348
366
|
method: GET
|
|
349
367
|
relations:
|
|
350
368
|
role:
|
|
351
369
|
- where:
|
|
352
370
|
slug: admin
|
|
371
|
+
- where:
|
|
372
|
+
slug: admin-user
|
|
353
373
|
- url: /user/:userId/role
|
|
354
374
|
method: PATCH
|
|
355
375
|
relations:
|
|
356
376
|
role:
|
|
357
377
|
- where:
|
|
358
378
|
slug: admin
|
|
379
|
+
- where:
|
|
380
|
+
slug: admin-user
|
|
359
381
|
- url: /user/:userId/role/:roleId
|
|
360
382
|
method: DELETE
|
|
361
383
|
relations:
|
|
362
384
|
role:
|
|
363
385
|
- where:
|
|
364
386
|
slug: admin
|
|
387
|
+
- where:
|
|
388
|
+
slug: admin-user
|
|
365
389
|
- url: /user/:userId/role/:roleId
|
|
366
390
|
method: POST
|
|
367
391
|
relations:
|
|
368
392
|
role:
|
|
369
393
|
- where:
|
|
370
394
|
slug: admin
|
|
395
|
+
- where:
|
|
396
|
+
slug: admin-user
|
|
371
397
|
- url: /user/:userId
|
|
372
398
|
method: GET
|
|
373
399
|
relations:
|
|
374
400
|
role:
|
|
375
401
|
- where:
|
|
376
402
|
slug: admin
|
|
403
|
+
- where:
|
|
404
|
+
slug: admin-user
|
|
377
405
|
- url: /user
|
|
378
406
|
method: POST
|
|
379
407
|
relations:
|
|
380
408
|
role:
|
|
381
409
|
- where:
|
|
382
410
|
slug: admin
|
|
411
|
+
- where:
|
|
412
|
+
slug: admin-user
|
|
383
413
|
- url: /user/:userId/avatar
|
|
384
414
|
method: POST
|
|
385
415
|
relations:
|
|
386
416
|
role:
|
|
387
417
|
- where:
|
|
388
418
|
slug: admin
|
|
419
|
+
- where:
|
|
420
|
+
slug: admin-user
|
|
389
421
|
- url: /user/:userId
|
|
390
422
|
method: PATCH
|
|
391
423
|
relations:
|
|
392
424
|
role:
|
|
393
425
|
- where:
|
|
394
426
|
slug: admin
|
|
427
|
+
- url: /user/:userId/reset-password
|
|
428
|
+
method: PATCH
|
|
429
|
+
relations:
|
|
430
|
+
role:
|
|
431
|
+
- where:
|
|
432
|
+
slug: admin
|
|
433
|
+
- where:
|
|
434
|
+
slug: admin-user
|
|
395
435
|
- url: /user
|
|
396
436
|
method: DELETE
|
|
397
437
|
relations:
|
|
398
438
|
role:
|
|
399
439
|
- where:
|
|
400
440
|
slug: admin
|
|
441
|
+
- where:
|
|
442
|
+
slug: admin-user
|
|
401
443
|
- url: /locale
|
|
402
444
|
method: GET
|
|
403
445
|
relations:
|
|
@@ -652,12 +694,16 @@
|
|
|
652
694
|
role:
|
|
653
695
|
- where:
|
|
654
696
|
slug: admin
|
|
697
|
+
- where:
|
|
698
|
+
slug: admin-access
|
|
655
699
|
- url: /dashboard-core/user-dashboards
|
|
656
700
|
method: GET
|
|
657
701
|
relations:
|
|
658
702
|
role:
|
|
659
703
|
- where:
|
|
660
704
|
slug: admin
|
|
705
|
+
- where:
|
|
706
|
+
slug: admin-access
|
|
661
707
|
- url: /dashboard-core/stats/overview/users
|
|
662
708
|
method: GET
|
|
663
709
|
relations:
|
|
@@ -670,18 +716,30 @@
|
|
|
670
716
|
role:
|
|
671
717
|
- where:
|
|
672
718
|
slug: admin-mail
|
|
719
|
+
- url: /dashboard-core/stats/overview/system
|
|
720
|
+
method: GET
|
|
721
|
+
relations:
|
|
722
|
+
role:
|
|
723
|
+
- where:
|
|
724
|
+
slug: admin
|
|
725
|
+
- where:
|
|
726
|
+
slug: admin-access
|
|
673
727
|
- url: /dashboard-core/layout/:slug
|
|
674
728
|
method: GET
|
|
675
729
|
relations:
|
|
676
730
|
role:
|
|
677
731
|
- where:
|
|
678
732
|
slug: admin
|
|
733
|
+
- where:
|
|
734
|
+
slug: admin-access
|
|
679
735
|
- url: /dashboard-core/layout/:slug
|
|
680
736
|
method: POST
|
|
681
737
|
relations:
|
|
682
738
|
role:
|
|
683
739
|
- where:
|
|
684
740
|
slug: admin
|
|
741
|
+
- where:
|
|
742
|
+
slug: admin-access
|
|
685
743
|
- url: /dashboard-core/widgets/me
|
|
686
744
|
method: GET
|
|
687
745
|
relations:
|
|
@@ -694,12 +752,16 @@
|
|
|
694
752
|
role:
|
|
695
753
|
- where:
|
|
696
754
|
slug: admin
|
|
755
|
+
- where:
|
|
756
|
+
slug: admin-access
|
|
697
757
|
- url: /dashboard-core/widget/:slug/:widgetId
|
|
698
758
|
method: DELETE
|
|
699
759
|
relations:
|
|
700
760
|
role:
|
|
701
761
|
- where:
|
|
702
762
|
slug: admin
|
|
763
|
+
- where:
|
|
764
|
+
slug: admin-access
|
|
703
765
|
- url: /dashboard
|
|
704
766
|
method: GET
|
|
705
767
|
relations:
|
|
@@ -742,6 +804,8 @@
|
|
|
742
804
|
role:
|
|
743
805
|
- where:
|
|
744
806
|
slug: admin
|
|
807
|
+
- where:
|
|
808
|
+
slug: admin-access
|
|
745
809
|
- url: /dashboard-component
|
|
746
810
|
method: POST
|
|
747
811
|
relations:
|
|
@@ -1325,4 +1389,4 @@
|
|
|
1325
1389
|
- where:
|
|
1326
1390
|
slug: admin
|
|
1327
1391
|
- where:
|
|
1328
|
-
slug: admin-access
|
|
1392
|
+
slug: admin-access
|
|
@@ -25,7 +25,7 @@ import { useForm } from 'react-hook-form';
|
|
|
25
25
|
import { z } from 'zod';
|
|
26
26
|
|
|
27
27
|
export function ChangePasswordForm() {
|
|
28
|
-
const { request, showToastHandler, getSettingValue } = useApp();
|
|
28
|
+
const { request, showToastHandler, getSettingValue, refetchUser } = useApp();
|
|
29
29
|
const t = useTranslations('core.ChangePasswordForm');
|
|
30
30
|
const minPasswordLength = getSettingValue('password-min-length') || 6;
|
|
31
31
|
const minPasswordSymbols = getSettingValue('password-min-symbols') || 0;
|
|
@@ -105,6 +105,7 @@ export function ChangePasswordForm() {
|
|
|
105
105
|
});
|
|
106
106
|
|
|
107
107
|
showToastHandler('success', t('updateSuccess'));
|
|
108
|
+
await refetchUser();
|
|
108
109
|
|
|
109
110
|
form.reset();
|
|
110
111
|
} catch (error) {
|
|
@@ -115,7 +115,7 @@ export default function AiAgentPage() {
|
|
|
115
115
|
},
|
|
116
116
|
});
|
|
117
117
|
|
|
118
|
-
const { data, isLoading, refetch } = useQuery<PaginatedResponse<Agent>>({
|
|
118
|
+
const { data, isLoading, refetch } = useQuery<PaginatedResponse<Agent>>({
|
|
119
119
|
queryKey: ['ai-agents', page, pageSize, debouncedSearch],
|
|
120
120
|
queryFn: async () => {
|
|
121
121
|
const params = new URLSearchParams();
|
|
@@ -130,14 +130,14 @@ export default function AiAgentPage() {
|
|
|
130
130
|
|
|
131
131
|
return response.data;
|
|
132
132
|
},
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}
|
|
133
|
+
});
|
|
134
|
+
const agentList = data ?? {
|
|
135
|
+
data: [],
|
|
136
|
+
total: 0,
|
|
137
|
+
page: 1,
|
|
138
|
+
pageSize: 10,
|
|
139
|
+
totalPages: 1,
|
|
140
|
+
};
|
|
141
141
|
|
|
142
142
|
useEffect(() => {
|
|
143
143
|
if (editingAgent) {
|
|
@@ -280,12 +280,12 @@ export default function AiAgentPage() {
|
|
|
280
280
|
<TableRow>
|
|
281
281
|
<TableCell colSpan={5}>{t('loading')}</TableCell>
|
|
282
282
|
</TableRow>
|
|
283
|
-
) :
|
|
284
|
-
<TableRow>
|
|
285
|
-
<TableCell colSpan={5}>{t('empty')}</TableCell>
|
|
286
|
-
</TableRow>
|
|
287
|
-
) : (
|
|
288
|
-
|
|
283
|
+
) : agentList.data.length === 0 ? (
|
|
284
|
+
<TableRow>
|
|
285
|
+
<TableCell colSpan={5}>{t('empty')}</TableCell>
|
|
286
|
+
</TableRow>
|
|
287
|
+
) : (
|
|
288
|
+
agentList.data.map((agent) => (
|
|
289
289
|
<TableRow key={agent.id}>
|
|
290
290
|
<TableCell>{agent.slug}</TableCell>
|
|
291
291
|
<TableCell className="uppercase">
|
|
@@ -322,8 +322,8 @@ export default function AiAgentPage() {
|
|
|
322
322
|
<PaginationFooter
|
|
323
323
|
currentPage={page}
|
|
324
324
|
pageSize={pageSize}
|
|
325
|
-
totalItems={
|
|
326
|
-
onPageChange={setPage}
|
|
325
|
+
totalItems={agentList.total}
|
|
326
|
+
onPageChange={setPage}
|
|
327
327
|
onPageSizeChange={(value) => {
|
|
328
328
|
setPageSize(value);
|
|
329
329
|
setPage(1);
|
|
@@ -12,6 +12,7 @@ import { Button } from '@/components/ui/button';
|
|
|
12
12
|
import { Separator } from '@/components/ui/separator';
|
|
13
13
|
import { SidebarTrigger } from '@/components/ui/sidebar';
|
|
14
14
|
import { Skeleton } from '@/components/ui/skeleton';
|
|
15
|
+
import { useIsMobile } from '@/components/ui/use-mobile';
|
|
15
16
|
import { useApp, useQuery } from '@hed-hog/next-app-provider';
|
|
16
17
|
import { IconDeviceFloppy } from '@tabler/icons-react';
|
|
17
18
|
import { useTranslations } from 'next-intl';
|
|
@@ -38,6 +39,7 @@ export const DashboardContent = ({ dashboardSlug }: DashboardContentProps) => {
|
|
|
38
39
|
const t = useTranslations('core.DashboardPage');
|
|
39
40
|
const { request } = useApp();
|
|
40
41
|
const router = useRouter();
|
|
42
|
+
const isMobile = useIsMobile();
|
|
41
43
|
|
|
42
44
|
const [layout, setLayout] = useState<LayoutItem[]>([]);
|
|
43
45
|
const [widgets, setWidgets] = useState<WidgetLayout[]>([]);
|
|
@@ -106,7 +108,10 @@ export const DashboardContent = ({ dashboardSlug }: DashboardContentProps) => {
|
|
|
106
108
|
x: item.x,
|
|
107
109
|
y: item.y,
|
|
108
110
|
w: item.w,
|
|
109
|
-
h:
|
|
111
|
+
h:
|
|
112
|
+
dashboardSlug === 'user' && item.slug === 'profile-card'
|
|
113
|
+
? Math.max(item.h, 3)
|
|
114
|
+
: item.h,
|
|
110
115
|
minW: item.minW || 1,
|
|
111
116
|
maxW: item.maxW || 12,
|
|
112
117
|
minH: item.minH || 1,
|
|
@@ -277,37 +282,42 @@ export const DashboardContent = ({ dashboardSlug }: DashboardContentProps) => {
|
|
|
277
282
|
|
|
278
283
|
return (
|
|
279
284
|
<>
|
|
280
|
-
<header className="flex h-16 shrink-0 items-center gap-2 transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-12">
|
|
281
|
-
<div className="flex w-full
|
|
282
|
-
<div className="flex items-center gap-2">
|
|
285
|
+
<header className="flex min-h-16 shrink-0 items-center gap-2 py-2 transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-12 sm:h-16 sm:py-0">
|
|
286
|
+
<div className="flex w-full flex-col gap-2 px-4 sm:flex-row sm:items-center sm:justify-between">
|
|
287
|
+
<div className="flex min-w-0 items-center gap-2">
|
|
283
288
|
<SidebarTrigger className="-ml-1" />
|
|
284
289
|
<Separator
|
|
285
290
|
orientation="vertical"
|
|
286
291
|
className="mr-2 data-[orientation=vertical]:h-4"
|
|
287
292
|
/>
|
|
288
|
-
<Breadcrumb>
|
|
293
|
+
<Breadcrumb className="min-w-0">
|
|
289
294
|
<BreadcrumbList>
|
|
290
295
|
<BreadcrumbItem className="hidden md:block">
|
|
291
296
|
<BreadcrumbLink href="#">{t('dashboard')}</BreadcrumbLink>
|
|
292
297
|
</BreadcrumbItem>
|
|
293
298
|
<BreadcrumbSeparator className="hidden md:block" />
|
|
294
299
|
<BreadcrumbItem>
|
|
295
|
-
<BreadcrumbPage>
|
|
300
|
+
<BreadcrumbPage className="truncate">
|
|
301
|
+
{dashboardName}
|
|
302
|
+
</BreadcrumbPage>
|
|
296
303
|
</BreadcrumbItem>
|
|
297
304
|
</BreadcrumbList>
|
|
298
305
|
</Breadcrumb>
|
|
299
306
|
</div>
|
|
300
|
-
<div className="flex items-center gap-2">
|
|
307
|
+
<div className="flex w-full items-center justify-end gap-2 sm:w-auto">
|
|
301
308
|
{hasChanges && (
|
|
302
309
|
<Button
|
|
303
310
|
size="sm"
|
|
304
311
|
variant="default"
|
|
305
|
-
className="gap-2"
|
|
312
|
+
className="gap-1 px-2 sm:gap-2 sm:px-3"
|
|
306
313
|
onClick={handleSaveLayout}
|
|
307
314
|
disabled={isSaving}
|
|
315
|
+
aria-label={isSaving ? t('saving') : t('saveLayout')}
|
|
308
316
|
>
|
|
309
317
|
<IconDeviceFloppy className="size-4" />
|
|
310
|
-
|
|
318
|
+
<span className="hidden sm:inline">
|
|
319
|
+
{isSaving ? t('saving') : t('saveLayout')}
|
|
320
|
+
</span>
|
|
311
321
|
</Button>
|
|
312
322
|
)}
|
|
313
323
|
<AddWidgetSelectorDialog
|
|
@@ -321,15 +331,16 @@ export const DashboardContent = ({ dashboardSlug }: DashboardContentProps) => {
|
|
|
321
331
|
</header>
|
|
322
332
|
<div className="flex flex-1 flex-col gap-4 overflow-auto p-4 pt-0">
|
|
323
333
|
{widgets.length > 0 ? (
|
|
324
|
-
<div className="min-h-[600px]">
|
|
334
|
+
<div className="min-h-[420px] sm:min-h-[520px] lg:min-h-[600px]">
|
|
325
335
|
<DraggableGrid
|
|
326
336
|
className="dashboard-grid"
|
|
327
337
|
layout={layout}
|
|
328
338
|
onLayoutChange={handleLayoutChange}
|
|
329
339
|
cols={12}
|
|
330
340
|
rowHeight={80}
|
|
331
|
-
|
|
332
|
-
|
|
341
|
+
preventCollision
|
|
342
|
+
isDraggable={!isMobile}
|
|
343
|
+
isResizable={!isMobile}
|
|
333
344
|
resizeHandles={['se']}
|
|
334
345
|
>
|
|
335
346
|
{widgets.map((widget) => (
|
|
@@ -36,6 +36,54 @@ interface DraggableGridProps {
|
|
|
36
36
|
resizeHandles?: Array<'s' | 'w' | 'e' | 'n' | 'sw' | 'nw' | 'se' | 'ne'>;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
const MOBILE_WIDTH = 640;
|
|
40
|
+
const TABLET_WIDTH = 1024;
|
|
41
|
+
|
|
42
|
+
const clamp = (value: number, min: number, max: number) =>
|
|
43
|
+
Math.max(min, Math.min(max, value));
|
|
44
|
+
|
|
45
|
+
const deriveResponsiveLayout = (
|
|
46
|
+
layout: Layout,
|
|
47
|
+
effectiveCols: number,
|
|
48
|
+
cols: number
|
|
49
|
+
): Layout => {
|
|
50
|
+
const sorted = [...layout].sort((a, b) => a.y - b.y || a.x - b.x);
|
|
51
|
+
const ratio = effectiveCols / cols;
|
|
52
|
+
|
|
53
|
+
let cursorX = 0;
|
|
54
|
+
let cursorY = 0;
|
|
55
|
+
let rowHeight = 0;
|
|
56
|
+
|
|
57
|
+
return sorted.map((item) => {
|
|
58
|
+
const nextW =
|
|
59
|
+
effectiveCols === 1
|
|
60
|
+
? 1
|
|
61
|
+
: clamp(Math.round(item.w * ratio), 1, effectiveCols);
|
|
62
|
+
const nextH = Math.max(1, item.h || 1);
|
|
63
|
+
|
|
64
|
+
if (cursorX + nextW > effectiveCols) {
|
|
65
|
+
cursorY += Math.max(1, rowHeight);
|
|
66
|
+
cursorX = 0;
|
|
67
|
+
rowHeight = 0;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const mappedItem: LayoutItem = {
|
|
71
|
+
...item,
|
|
72
|
+
x: cursorX,
|
|
73
|
+
y: cursorY,
|
|
74
|
+
w: nextW,
|
|
75
|
+
h: nextH,
|
|
76
|
+
minW: clamp(Math.min(item.minW || 1, nextW), 1, nextW),
|
|
77
|
+
maxW: clamp(item.maxW || effectiveCols, nextW, effectiveCols),
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
cursorX += nextW;
|
|
81
|
+
rowHeight = Math.max(rowHeight, nextH);
|
|
82
|
+
|
|
83
|
+
return mappedItem;
|
|
84
|
+
});
|
|
85
|
+
};
|
|
86
|
+
|
|
39
87
|
// Simple compaction function for grid layout
|
|
40
88
|
const compactLayout = (
|
|
41
89
|
layout: RGLLayout,
|
|
@@ -64,7 +112,7 @@ export function DraggableGrid({
|
|
|
64
112
|
isDraggable = true,
|
|
65
113
|
isResizable = true,
|
|
66
114
|
compactType = 'vertical',
|
|
67
|
-
preventCollision =
|
|
115
|
+
preventCollision = true,
|
|
68
116
|
margin = [16, 16],
|
|
69
117
|
containerPadding = [0, 0],
|
|
70
118
|
resizeHandles = ['se'],
|
|
@@ -72,6 +120,29 @@ export function DraggableGrid({
|
|
|
72
120
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
73
121
|
const [containerWidth, setContainerWidth] = useState(1200);
|
|
74
122
|
|
|
123
|
+
const effectiveCols =
|
|
124
|
+
containerWidth < MOBILE_WIDTH
|
|
125
|
+
? 1
|
|
126
|
+
: containerWidth < TABLET_WIDTH
|
|
127
|
+
? Math.min(6, cols)
|
|
128
|
+
: cols;
|
|
129
|
+
|
|
130
|
+
const effectiveRowHeight =
|
|
131
|
+
containerWidth < MOBILE_WIDTH ? Math.max(76, rowHeight - 4) : rowHeight;
|
|
132
|
+
|
|
133
|
+
const effectiveMargin: [number, number] =
|
|
134
|
+
containerWidth < MOBILE_WIDTH
|
|
135
|
+
? [Math.min(10, margin[0]), Math.min(10, margin[1])]
|
|
136
|
+
: margin;
|
|
137
|
+
|
|
138
|
+
const responsiveLayout = (() => {
|
|
139
|
+
if (effectiveCols === cols) {
|
|
140
|
+
return layout;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return deriveResponsiveLayout(layout, effectiveCols, cols);
|
|
144
|
+
})();
|
|
145
|
+
|
|
75
146
|
useEffect(() => {
|
|
76
147
|
const updateWidth = () => {
|
|
77
148
|
if (containerRef.current) {
|
|
@@ -91,6 +162,10 @@ export function DraggableGrid({
|
|
|
91
162
|
}, []);
|
|
92
163
|
|
|
93
164
|
const handleLayoutChange = (newLayout: RGLLayout) => {
|
|
165
|
+
if (effectiveCols !== cols) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
94
169
|
const layouts = Array.isArray(newLayout) ? newLayout : [newLayout];
|
|
95
170
|
const convertedLayout = layouts.map((item: any) => ({
|
|
96
171
|
i: item.i,
|
|
@@ -111,12 +186,12 @@ export function DraggableGrid({
|
|
|
111
186
|
<div ref={containerRef} className="w-full">
|
|
112
187
|
<GridLayout
|
|
113
188
|
className={`layout ${className}`}
|
|
114
|
-
layout={
|
|
189
|
+
layout={responsiveLayout}
|
|
115
190
|
gridConfig={{
|
|
116
|
-
cols,
|
|
117
|
-
rowHeight,
|
|
191
|
+
cols: effectiveCols,
|
|
192
|
+
rowHeight: effectiveRowHeight,
|
|
118
193
|
containerPadding,
|
|
119
|
-
margin,
|
|
194
|
+
margin: effectiveMargin,
|
|
120
195
|
}}
|
|
121
196
|
dragConfig={{
|
|
122
197
|
enabled: isDraggable,
|
|
@@ -59,7 +59,7 @@ function AccountSecurityContent({ data }: { data: AccountSecurityData }) {
|
|
|
59
59
|
: 'hsl(0, 84%, 60%)';
|
|
60
60
|
|
|
61
61
|
return (
|
|
62
|
-
<Card className="flex h-full flex-col">
|
|
62
|
+
<Card className="flex h-full min-h-0 flex-col overflow-hidden">
|
|
63
63
|
<CardHeader className="shrink-0 pb-3">
|
|
64
64
|
<div className="flex items-center gap-2">
|
|
65
65
|
<ShieldCheck className="h-5 w-5 text-emerald-600 dark:text-emerald-400" />
|
|
@@ -71,13 +71,17 @@ function AccountSecurityContent({ data }: { data: AccountSecurityData }) {
|
|
|
71
71
|
</div>
|
|
72
72
|
</div>
|
|
73
73
|
</CardHeader>
|
|
74
|
-
<CardContent className="flex-1 overflow-auto pt-0">
|
|
75
|
-
<div className="mb-
|
|
74
|
+
<CardContent className="flex min-h-0 flex-1 flex-col overflow-auto pt-0">
|
|
75
|
+
<div className="mb-4 flex flex-col items-center gap-2.5 rounded-xl bg-muted/50 p-3 sm:mb-5 sm:gap-3 sm:p-5">
|
|
76
76
|
<div className="flex items-baseline gap-1">
|
|
77
|
-
<span
|
|
77
|
+
<span
|
|
78
|
+
className={`text-4xl font-bold tracking-tight sm:text-5xl ${scoreColor}`}
|
|
79
|
+
>
|
|
78
80
|
{score}
|
|
79
81
|
</span>
|
|
80
|
-
<span className="text-
|
|
82
|
+
<span className="text-base text-muted-foreground sm:text-lg">
|
|
83
|
+
/100
|
|
84
|
+
</span>
|
|
81
85
|
</div>
|
|
82
86
|
<Progress
|
|
83
87
|
value={score}
|
|
@@ -88,7 +92,7 @@ function AccountSecurityContent({ data }: { data: AccountSecurityData }) {
|
|
|
88
92
|
} as any
|
|
89
93
|
}
|
|
90
94
|
/>
|
|
91
|
-
<p className="text-
|
|
95
|
+
<p className="text-[11px] text-muted-foreground sm:text-xs">
|
|
92
96
|
{score >= 80 ? t('wellProtected') : t('recommendProtections')}
|
|
93
97
|
</p>
|
|
94
98
|
</div>
|
|
@@ -99,17 +103,17 @@ function AccountSecurityContent({ data }: { data: AccountSecurityData }) {
|
|
|
99
103
|
return (
|
|
100
104
|
<div
|
|
101
105
|
key={item.id}
|
|
102
|
-
className="group flex items-
|
|
106
|
+
className="group flex flex-wrap items-start gap-2.5 rounded-lg p-2.5 transition-colors hover:bg-muted/50 sm:items-center sm:gap-3 sm:p-3"
|
|
103
107
|
>
|
|
104
108
|
<div
|
|
105
|
-
className={`flex h-
|
|
109
|
+
className={`flex h-8 w-8 shrink-0 items-center justify-center rounded-lg sm:h-9 sm:w-9 ${
|
|
106
110
|
item.enabled
|
|
107
111
|
? 'bg-emerald-50 dark:bg-emerald-950/40'
|
|
108
112
|
: 'bg-muted'
|
|
109
113
|
}`}
|
|
110
114
|
>
|
|
111
115
|
<Icon
|
|
112
|
-
className={`h-4 w-4 ${
|
|
116
|
+
className={`h-3.5 w-3.5 sm:h-4 sm:w-4 ${
|
|
113
117
|
item.enabled
|
|
114
118
|
? 'text-emerald-600 dark:text-emerald-400'
|
|
115
119
|
: 'text-muted-foreground'
|
|
@@ -117,8 +121,8 @@ function AccountSecurityContent({ data }: { data: AccountSecurityData }) {
|
|
|
117
121
|
/>
|
|
118
122
|
</div>
|
|
119
123
|
<div className="flex min-w-0 flex-1 flex-col">
|
|
120
|
-
<div className="flex items-center gap-2">
|
|
121
|
-
<span className="text-
|
|
124
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
125
|
+
<span className="wrap-break-word text-[13px] font-medium text-foreground sm:text-sm">
|
|
122
126
|
{t(`labels.${item.labelKey}` as any) || item.labelKey}
|
|
123
127
|
</span>
|
|
124
128
|
{item.enabled ? (
|
|
@@ -127,7 +131,7 @@ function AccountSecurityContent({ data }: { data: AccountSecurityData }) {
|
|
|
127
131
|
<AlertTriangle className="h-3.5 w-3.5 text-amber-500" />
|
|
128
132
|
)}
|
|
129
133
|
</div>
|
|
130
|
-
<span className="text-
|
|
134
|
+
<span className="text-[11px] text-muted-foreground sm:text-xs">
|
|
131
135
|
{t(`descriptions.${item.descriptionKey}` as any) ||
|
|
132
136
|
item.descriptionKey}
|
|
133
137
|
</span>
|
|
@@ -137,7 +141,7 @@ function AccountSecurityContent({ data }: { data: AccountSecurityData }) {
|
|
|
137
141
|
variant="ghost"
|
|
138
142
|
size="sm"
|
|
139
143
|
onClick={() => router.push('/core/account/2fa')}
|
|
140
|
-
className="shrink-0 gap-1 text-xs"
|
|
144
|
+
className="mt-1 w-full shrink-0 gap-1 text-xs sm:mt-0 sm:w-auto"
|
|
141
145
|
>
|
|
142
146
|
{t('activate')}
|
|
143
147
|
<ChevronRight className="h-3 w-3" />
|
|
@@ -119,24 +119,26 @@ function TimelineContent({ events }: { events: ActivityEvent[] }) {
|
|
|
119
119
|
let lastDate = '';
|
|
120
120
|
|
|
121
121
|
return (
|
|
122
|
-
<Card className="flex h-full flex-col">
|
|
122
|
+
<Card className="flex h-full min-h-0 flex-col overflow-hidden">
|
|
123
123
|
<CardHeader className="shrink-0 pb-3">
|
|
124
124
|
<div className="flex items-center justify-between">
|
|
125
125
|
<div>
|
|
126
|
-
<CardTitle className="text-
|
|
126
|
+
<CardTitle className="text-sm font-semibold sm:text-base">
|
|
127
127
|
{t('title')}
|
|
128
128
|
</CardTitle>
|
|
129
|
-
<CardDescription>
|
|
129
|
+
<CardDescription className="text-xs sm:text-sm">
|
|
130
|
+
{t('description')}
|
|
131
|
+
</CardDescription>
|
|
130
132
|
</div>
|
|
131
|
-
<Badge variant="secondary">
|
|
133
|
+
<Badge variant="secondary" className="text-[10px] sm:text-xs">
|
|
132
134
|
{t('events', { count: events.length })}
|
|
133
135
|
</Badge>
|
|
134
136
|
</div>
|
|
135
137
|
</CardHeader>
|
|
136
138
|
<CardContent className="flex min-h-0 flex-1 flex-col overflow-hidden pt-0">
|
|
137
|
-
<ScrollArea className="h-full pr-3">
|
|
139
|
+
<ScrollArea className="h-full pr-2 sm:pr-3">
|
|
138
140
|
<div className="relative flex flex-col pb-2">
|
|
139
|
-
<div className="absolute bottom-0 left-[
|
|
141
|
+
<div className="absolute bottom-0 left-[13px] top-0 w-px bg-border/60 sm:left-[15px]" />
|
|
140
142
|
{events.map((event, index) => {
|
|
141
143
|
const type = detectType(event.action);
|
|
142
144
|
const config =
|
|
@@ -155,23 +157,25 @@ function TimelineContent({ events }: { events: ActivityEvent[] }) {
|
|
|
155
157
|
<div key={event.id}>
|
|
156
158
|
{showDate && (
|
|
157
159
|
<div className="relative z-10 mb-1 mt-4 first:mt-0">
|
|
158
|
-
<span className="ml-
|
|
160
|
+
<span className="ml-9 inline-block rounded-md border border-border bg-muted px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground sm:ml-11 sm:px-2 sm:text-[11px]">
|
|
159
161
|
{dateLabel}
|
|
160
162
|
</span>
|
|
161
163
|
</div>
|
|
162
164
|
)}
|
|
163
165
|
<div className="group relative flex items-start gap-3 py-1.5">
|
|
164
166
|
<div
|
|
165
|
-
className={`relative z-10 mt-0.5 flex h-
|
|
167
|
+
className={`relative z-10 mt-0.5 flex h-7 w-7 shrink-0 items-center justify-center rounded-lg sm:h-8 sm:w-8 ${config.bg}`}
|
|
166
168
|
>
|
|
167
|
-
<Icon
|
|
169
|
+
<Icon
|
|
170
|
+
className={`h-3 w-3 sm:h-3.5 sm:w-3.5 ${config.color}`}
|
|
171
|
+
/>
|
|
168
172
|
</div>
|
|
169
|
-
<div className="flex min-w-0 flex-1 flex-col gap-0.5 rounded-lg px-
|
|
173
|
+
<div className="flex min-w-0 flex-1 flex-col gap-0.5 rounded-lg px-1.5 py-1.5 transition-colors group-hover:bg-muted/40 sm:px-2">
|
|
170
174
|
<div className="flex items-center justify-between gap-2">
|
|
171
|
-
<span className="text-
|
|
175
|
+
<span className="min-w-0 wrap-break-word text-xs font-medium leading-snug text-foreground sm:text-sm">
|
|
172
176
|
{event.action}
|
|
173
177
|
</span>
|
|
174
|
-
<span className="shrink-0 text-[
|
|
178
|
+
<span className="shrink-0 text-[10px] tabular-nums text-muted-foreground sm:text-[11px]">
|
|
175
179
|
{formatTime(event.created_at)}
|
|
176
180
|
</span>
|
|
177
181
|
</div>
|