@hed-hog/core 0.0.185 → 0.0.190

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 (55) hide show
  1. package/hedhog/frontend/app/account/2fa/page.tsx.ejs +5 -0
  2. package/hedhog/frontend/app/account/accounts/page.tsx.ejs +5 -0
  3. package/hedhog/frontend/app/account/components/active-sessions.tsx.ejs +356 -0
  4. package/hedhog/frontend/app/account/components/change-email-form.tsx.ejs +379 -0
  5. package/hedhog/frontend/app/account/components/change-password-form.tsx.ejs +184 -0
  6. package/hedhog/frontend/app/account/components/connected-accounts.tsx.ejs +144 -0
  7. package/hedhog/frontend/app/account/components/email-request-dialog.tsx.ejs +96 -0
  8. package/hedhog/frontend/app/account/components/mfa-add-buttons.tsx.ejs +43 -0
  9. package/hedhog/frontend/app/account/components/mfa-method-card.tsx.ejs +115 -0
  10. package/hedhog/frontend/app/account/components/mfa-setup-dialog.tsx.ejs +236 -0
  11. package/hedhog/frontend/app/account/components/profile-form.tsx.ejs +209 -0
  12. package/hedhog/frontend/app/account/components/recovery-codes-dialog.tsx.ejs +192 -0
  13. package/hedhog/frontend/app/account/components/regenerate-codes-dialog.tsx.ejs +372 -0
  14. package/hedhog/frontend/app/account/components/remove-mfa-dialog.tsx.ejs +337 -0
  15. package/hedhog/frontend/app/account/components/two-factor-auth.tsx.ejs +393 -0
  16. package/hedhog/frontend/app/account/components/verify-before-add-dialog.tsx.ejs +332 -0
  17. package/hedhog/frontend/app/account/email/page.tsx.ejs +5 -0
  18. package/hedhog/frontend/app/account/hooks/use-mfa-methods.ts.ejs +27 -0
  19. package/hedhog/frontend/app/account/hooks/use-mfa-setup.ts.ejs +461 -0
  20. package/hedhog/frontend/app/account/layout.tsx.ejs +105 -0
  21. package/hedhog/frontend/app/account/lib/mfa-utils.tsx.ejs +37 -0
  22. package/hedhog/frontend/app/account/page.tsx.ejs +5 -0
  23. package/hedhog/frontend/app/account/password/page.tsx.ejs +5 -0
  24. package/hedhog/frontend/app/account/profile/page.tsx.ejs +5 -0
  25. package/hedhog/frontend/app/account/sessions/page.tsx.ejs +5 -0
  26. package/hedhog/frontend/app/configurations/[slug]/components/setting-field.tsx.ejs +490 -0
  27. package/hedhog/frontend/app/configurations/[slug]/page.tsx.ejs +62 -0
  28. package/hedhog/frontend/app/configurations/layout.tsx.ejs +316 -0
  29. package/hedhog/frontend/app/configurations/page.tsx.ejs +35 -0
  30. package/hedhog/frontend/app/dashboard/[slug]/dashboard-content.tsx.ejs +351 -0
  31. package/hedhog/frontend/app/dashboard/[slug]/page.tsx.ejs +11 -0
  32. package/hedhog/frontend/app/dashboard/[slug]/types.ts.ejs +62 -0
  33. package/hedhog/frontend/app/dashboard/[slug]/widget-renderer.tsx.ejs +45 -0
  34. package/hedhog/frontend/app/dashboard/dashboard.css.ejs +196 -0
  35. package/hedhog/frontend/app/dashboard/management/page.tsx.ejs +63 -0
  36. package/hedhog/frontend/app/dashboard/management/tabs/component-roles-tab.tsx.ejs +516 -0
  37. package/hedhog/frontend/app/dashboard/management/tabs/components-tab.tsx.ejs +753 -0
  38. package/hedhog/frontend/app/dashboard/management/tabs/dashboard-roles-tab.tsx.ejs +516 -0
  39. package/hedhog/frontend/app/dashboard/management/tabs/dashboards-tab.tsx.ejs +489 -0
  40. package/hedhog/frontend/app/dashboard/management/tabs/items-tab.tsx.ejs +621 -0
  41. package/hedhog/frontend/app/dashboard/page.tsx.ejs +14 -0
  42. package/hedhog/frontend/app/mail/log/page.tsx.ejs +312 -0
  43. package/hedhog/frontend/app/mail/template/page.tsx.ejs +1177 -0
  44. package/hedhog/frontend/app/preferences/page.tsx.ejs +448 -0
  45. package/hedhog/frontend/app/roles/menus.tsx.ejs +504 -0
  46. package/hedhog/frontend/app/roles/page.tsx.ejs +814 -0
  47. package/hedhog/frontend/app/roles/routes.tsx.ejs +397 -0
  48. package/hedhog/frontend/app/roles/users.tsx.ejs +306 -0
  49. package/hedhog/frontend/app/users/active-session.tsx.ejs +159 -0
  50. package/hedhog/frontend/app/users/identifiers.tsx.ejs +279 -0
  51. package/hedhog/frontend/app/users/page.tsx.ejs +1257 -0
  52. package/hedhog/frontend/app/users/permissions.tsx.ejs +155 -0
  53. package/hedhog/frontend/messages/en.json +1080 -0
  54. package/hedhog/frontend/messages/pt.json +1135 -0
  55. package/package.json +4 -4
@@ -0,0 +1,516 @@
1
+ 'use client';
2
+
3
+ import {
4
+ AlertDialog,
5
+ AlertDialogAction,
6
+ AlertDialogCancel,
7
+ AlertDialogContent,
8
+ AlertDialogDescription,
9
+ AlertDialogFooter,
10
+ AlertDialogHeader,
11
+ AlertDialogTitle,
12
+ } from '@/components/ui/alert-dialog';
13
+ import { Button } from '@/components/ui/button';
14
+ import {
15
+ Dialog,
16
+ DialogContent,
17
+ DialogDescription,
18
+ DialogFooter,
19
+ DialogHeader,
20
+ DialogTitle,
21
+ DialogTrigger,
22
+ } from '@/components/ui/dialog';
23
+ import {
24
+ Form,
25
+ FormControl,
26
+ FormField,
27
+ FormItem,
28
+ FormLabel,
29
+ FormMessage,
30
+ } from '@/components/ui/form';
31
+ import { Label } from '@/components/ui/label';
32
+ import {
33
+ Select,
34
+ SelectContent,
35
+ SelectItem,
36
+ SelectTrigger,
37
+ SelectValue,
38
+ } from '@/components/ui/select';
39
+ import {
40
+ Table,
41
+ TableBody,
42
+ TableCell,
43
+ TableHead,
44
+ TableHeader,
45
+ TableRow,
46
+ } from '@/components/ui/table';
47
+ import { useDebounce } from '@/hooks/use-debounce';
48
+ import { useApp, useQuery } from '@hed-hog/next-app-provider';
49
+ import { zodResolver } from '@hookform/resolvers/zod';
50
+ import {
51
+ IconChevronLeft,
52
+ IconChevronRight,
53
+ IconChevronsLeft,
54
+ IconChevronsRight,
55
+ IconPlus,
56
+ IconTrash,
57
+ } from '@tabler/icons-react';
58
+ import { useTranslations } from 'next-intl';
59
+ import { useState } from 'react';
60
+ import { useForm } from 'react-hook-form';
61
+ import { toast } from 'sonner';
62
+ import { z } from 'zod';
63
+
64
+ type RoleFormData = {
65
+ component_id: number;
66
+ role_id: number;
67
+ };
68
+
69
+ interface ComponentRole {
70
+ id: number;
71
+ component_id: number;
72
+ role_id: number;
73
+ dashboard_component: {
74
+ slug: string;
75
+ dashboard_component_locale: Array<{
76
+ locale: { code: string };
77
+ name: string;
78
+ }>;
79
+ };
80
+ role: {
81
+ slug: string;
82
+ role_locale: Array<{
83
+ locale: { code: string };
84
+ name: string;
85
+ }>;
86
+ };
87
+ }
88
+
89
+ interface Component {
90
+ id: number;
91
+ slug: string;
92
+ dashboard_component_locale: Array<{
93
+ locale: { code: string };
94
+ name: string;
95
+ }>;
96
+ }
97
+
98
+ interface Role {
99
+ id: number;
100
+ role_id?: number;
101
+ slug: string;
102
+ name?: string;
103
+ role_locale: Array<{
104
+ locale: { code: string };
105
+ name: string;
106
+ }>;
107
+ }
108
+
109
+ export function ComponentRolesTab() {
110
+ const t = useTranslations('core.DashboardManagement');
111
+ const { request, currentLocaleCode } = useApp();
112
+
113
+ const roleSchema = z.object({
114
+ component_id: z.coerce.number().min(1, t('selectComponentRequired')),
115
+ role_id: z.coerce.number().min(1, t('selectRoleRequired')),
116
+ });
117
+
118
+ const [open, setOpen] = useState(false);
119
+ const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
120
+ const [roleToDelete, setRoleToDelete] = useState<number | null>(null);
121
+ const [page, setPage] = useState(1);
122
+ const [pageSize, setPageSize] = useState(10);
123
+ const [searchQuery, setSearchQuery] = useState('');
124
+ const debouncedSearch = useDebounce(searchQuery, 500);
125
+
126
+ const {
127
+ data: paginatedData,
128
+ isLoading,
129
+ refetch,
130
+ } = useQuery<{ data: ComponentRole[]; total: number }>({
131
+ queryKey: [
132
+ 'dashboard-component-roles',
133
+ page,
134
+ pageSize,
135
+ debouncedSearch,
136
+ currentLocaleCode,
137
+ ],
138
+ queryFn: async () => {
139
+ const params = new URLSearchParams();
140
+ params.set('page', String(page));
141
+ params.set('pageSize', String(pageSize));
142
+ if (debouncedSearch) params.set('search', debouncedSearch);
143
+
144
+ const response = await request<{ data: ComponentRole[]; total: number }>({
145
+ url: `/dashboard-component-role?${params.toString()}`,
146
+ method: 'GET',
147
+ });
148
+ return response.data;
149
+ },
150
+ });
151
+
152
+ const roles = paginatedData?.data ?? [];
153
+ const total = paginatedData?.total ?? 0;
154
+ const totalPages = Math.ceil(total / pageSize);
155
+
156
+ const { data: components } = useQuery<any>({
157
+ queryKey: ['dashboard-components'],
158
+ queryFn: async () => {
159
+ const { data } = await request<Component[]>({
160
+ url: '/dashboard-component',
161
+ method: 'GET',
162
+ });
163
+ return data;
164
+ },
165
+ });
166
+
167
+ const { data: availableRoles } = useQuery<any>({
168
+ queryKey: ['roles'],
169
+ queryFn: async () => {
170
+ const { data } = await request<Role[]>({
171
+ url: '/role',
172
+ method: 'GET',
173
+ });
174
+ return data;
175
+ },
176
+ });
177
+
178
+ const form = useForm<RoleFormData>({
179
+ resolver: zodResolver(roleSchema),
180
+ defaultValues: {
181
+ component_id: 0,
182
+ role_id: 0,
183
+ },
184
+ });
185
+
186
+ const onSubmit = async (values: RoleFormData) => {
187
+ try {
188
+ await request({
189
+ url: '/dashboard-component-role',
190
+ method: 'POST',
191
+ data: {
192
+ component_id: values.component_id,
193
+ role_id: values.role_id,
194
+ },
195
+ });
196
+ toast.success(t('roleAdded'));
197
+ setOpen(false);
198
+ form.reset();
199
+ refetch();
200
+ } catch (error: any) {
201
+ console.error('Erro ao adicionar permissão:', error);
202
+ if (error?.response?.data?.message?.includes('already exists')) {
203
+ toast.error(t('roleAlreadyExists'));
204
+ } else {
205
+ toast.error(t('errorAddingRole'));
206
+ }
207
+ }
208
+ };
209
+
210
+ const handleDeleteClick = (id: number) => {
211
+ setRoleToDelete(id);
212
+ setDeleteDialogOpen(true);
213
+ };
214
+
215
+ const handleDeleteConfirm = async () => {
216
+ if (!roleToDelete) return;
217
+
218
+ try {
219
+ await request({
220
+ url: `/dashboard-component-role/${roleToDelete}`,
221
+ method: 'DELETE',
222
+ });
223
+ toast.success(t('roleDeleted'));
224
+ refetch();
225
+ } catch (error) {
226
+ console.error('Erro ao excluir permissão:', error);
227
+ toast.error(t('errorDeletingRole'));
228
+ } finally {
229
+ setDeleteDialogOpen(false);
230
+ setRoleToDelete(null);
231
+ }
232
+ };
233
+
234
+ const handleOpenChange = (newOpen: boolean) => {
235
+ setOpen(newOpen);
236
+ if (!newOpen) {
237
+ form.reset();
238
+ }
239
+ };
240
+
241
+ return (
242
+ <div className="space-y-4">
243
+ <div className="flex justify-between items-center">
244
+ <div>
245
+ <h2 className="text-lg font-semibold">{t('componentRolesTab')}</h2>
246
+ <p className="text-muted-foreground text-sm">
247
+ {t('manageComponentRoles')}
248
+ </p>
249
+ </div>
250
+ <Dialog open={open} onOpenChange={handleOpenChange}>
251
+ <DialogTrigger asChild>
252
+ <Button size="sm" className="gap-2">
253
+ <IconPlus className="size-4" />
254
+ {t('addRole')}
255
+ </Button>
256
+ </DialogTrigger>
257
+ <DialogContent className="max-w-2xl">
258
+ <DialogHeader>
259
+ <DialogTitle>{t('addRole')}</DialogTitle>
260
+ <DialogDescription>
261
+ {t('selectComponentAndRole')}
262
+ </DialogDescription>
263
+ </DialogHeader>
264
+ <Form {...form}>
265
+ <form
266
+ onSubmit={form.handleSubmit(onSubmit)}
267
+ className="space-y-4"
268
+ >
269
+ <FormField
270
+ control={form.control}
271
+ name="component_id"
272
+ render={({ field }) => (
273
+ <FormItem>
274
+ <FormLabel>{t('component')}</FormLabel>
275
+ <Select
276
+ onValueChange={field.onChange}
277
+ defaultValue={String(field.value)}
278
+ >
279
+ <FormControl>
280
+ <SelectTrigger className="w-full">
281
+ <SelectValue placeholder={t('selectComponent')} />
282
+ </SelectTrigger>
283
+ </FormControl>
284
+ <SelectContent>
285
+ {(components?.data ?? []).map(
286
+ (component: Component) => {
287
+ const name =
288
+ component.dashboard_component_locale.find(
289
+ (l) => l.locale.code === currentLocaleCode
290
+ )?.name || component.slug;
291
+ return (
292
+ <SelectItem
293
+ key={component.id}
294
+ value={String(component.id)}
295
+ >
296
+ {name} ({component.slug})
297
+ </SelectItem>
298
+ );
299
+ }
300
+ )}
301
+ </SelectContent>
302
+ </Select>
303
+ <FormMessage />
304
+ </FormItem>
305
+ )}
306
+ />
307
+ <FormField
308
+ control={form.control}
309
+ name="role_id"
310
+ render={({ field }) => (
311
+ <FormItem>
312
+ <FormLabel>{t('role')}</FormLabel>
313
+ <Select
314
+ onValueChange={field.onChange}
315
+ defaultValue={String(field.value)}
316
+ >
317
+ <FormControl>
318
+ <SelectTrigger className="w-full">
319
+ <SelectValue placeholder={t('selectRole')} />
320
+ </SelectTrigger>
321
+ </FormControl>
322
+ <SelectContent>
323
+ {(availableRoles?.data ?? []).map((role: Role) => {
324
+ return (
325
+ <SelectItem
326
+ key={role.role_id}
327
+ value={String(role.role_id)}
328
+ >
329
+ {role.name} ({role.slug})
330
+ </SelectItem>
331
+ );
332
+ })}
333
+ </SelectContent>
334
+ </Select>
335
+ <FormMessage />
336
+ </FormItem>
337
+ )}
338
+ />
339
+
340
+ <DialogFooter>
341
+ <Button type="submit">{t('save')}</Button>
342
+ </DialogFooter>
343
+ </form>
344
+ </Form>
345
+ </DialogContent>
346
+ </Dialog>
347
+ </div>
348
+
349
+ <div className="rounded-md border">
350
+ <Table>
351
+ <TableHeader>
352
+ <TableRow>
353
+ <TableHead>ID</TableHead>
354
+ <TableHead>{t('component')}</TableHead>
355
+ <TableHead>{t('role')}</TableHead>
356
+ <TableHead className="w-[100px]">{t('actions')}</TableHead>
357
+ </TableRow>
358
+ </TableHeader>
359
+ <TableBody>
360
+ {isLoading ? (
361
+ <TableRow>
362
+ <TableCell colSpan={4} className="text-center">
363
+ {t('loading')}
364
+ </TableCell>
365
+ </TableRow>
366
+ ) : roles && roles.length > 0 ? (
367
+ roles.map((role) => {
368
+ const componentName =
369
+ role.dashboard_component.dashboard_component_locale.find(
370
+ (l) => l.locale.code === currentLocaleCode
371
+ )?.name || role.dashboard_component.slug;
372
+
373
+ const roleName =
374
+ role.role.role_locale.find(
375
+ (l) => l.locale.code === currentLocaleCode
376
+ )?.name || role.role.slug;
377
+
378
+ return (
379
+ <TableRow key={role.id}>
380
+ <TableCell>{role.id}</TableCell>
381
+ <TableCell>
382
+ {componentName}
383
+ <span className="text-muted-foreground ml-2 text-xs">
384
+ ({role.dashboard_component.slug})
385
+ </span>
386
+ </TableCell>
387
+ <TableCell>
388
+ {roleName}
389
+ <span className="text-muted-foreground ml-2 text-xs">
390
+ ({role.role.slug})
391
+ </span>
392
+ </TableCell>
393
+ <TableCell>
394
+ <Button
395
+ size="icon"
396
+ variant="ghost"
397
+ onClick={() => handleDeleteClick(role.id)}
398
+ >
399
+ <IconTrash className="size-4" />
400
+ </Button>
401
+ </TableCell>
402
+ </TableRow>
403
+ );
404
+ })
405
+ ) : (
406
+ <TableRow>
407
+ <TableCell colSpan={4} className="text-center">
408
+ {t('noRoles')}
409
+ </TableCell>
410
+ </TableRow>
411
+ )}
412
+ </TableBody>
413
+ </Table>
414
+ </div>
415
+
416
+ {/* Paginação */}
417
+ {totalPages > 1 && (
418
+ <div className="flex items-center justify-between px-2">
419
+ <div className="text-muted-foreground text-sm">
420
+ {t('showingResults', {
421
+ from: (page - 1) * pageSize + 1,
422
+ to: Math.min(page * pageSize, total),
423
+ total: total,
424
+ })}
425
+ </div>
426
+ <div className="flex items-center gap-2">
427
+ <div className="flex items-center gap-2">
428
+ <Label htmlFor="pageSize" className="text-sm">
429
+ {t('itemsPerPage')}
430
+ </Label>
431
+ <Select
432
+ value={String(pageSize)}
433
+ onValueChange={(value) => {
434
+ setPageSize(Number(value));
435
+ setPage(1);
436
+ }}
437
+ >
438
+ <SelectTrigger className="w-20" id="pageSize">
439
+ <SelectValue />
440
+ </SelectTrigger>
441
+ <SelectContent>
442
+ {[10, 20, 30, 50].map((size) => (
443
+ <SelectItem key={size} value={String(size)}>
444
+ {size}
445
+ </SelectItem>
446
+ ))}
447
+ </SelectContent>
448
+ </Select>
449
+ </div>
450
+ <div className="text-sm">
451
+ {t('pageOf', { page: page, total: totalPages })}
452
+ </div>
453
+ <div className="flex gap-1">
454
+ <Button
455
+ variant="outline"
456
+ size="icon"
457
+ className="size-8"
458
+ onClick={() => setPage(1)}
459
+ disabled={page === 1}
460
+ >
461
+ <IconChevronsLeft className="size-4" />
462
+ </Button>
463
+ <Button
464
+ variant="outline"
465
+ size="icon"
466
+ className="size-8"
467
+ onClick={() => setPage((p) => Math.max(1, p - 1))}
468
+ disabled={page === 1}
469
+ >
470
+ <IconChevronLeft className="size-4" />
471
+ </Button>
472
+ <Button
473
+ variant="outline"
474
+ size="icon"
475
+ className="size-8"
476
+ onClick={() => setPage((p) => Math.min(totalPages, p + 1))}
477
+ disabled={page === totalPages}
478
+ >
479
+ <IconChevronRight className="size-4" />
480
+ </Button>
481
+ <Button
482
+ variant="outline"
483
+ size="icon"
484
+ className="size-8"
485
+ onClick={() => setPage(totalPages)}
486
+ disabled={page === totalPages}
487
+ >
488
+ <IconChevronsRight className="size-4" />
489
+ </Button>
490
+ </div>
491
+ </div>
492
+ </div>
493
+ )}
494
+
495
+ <AlertDialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
496
+ <AlertDialogContent>
497
+ <AlertDialogHeader>
498
+ <AlertDialogTitle>{t('confirmDelete')}</AlertDialogTitle>
499
+ <AlertDialogDescription>
500
+ {t('deleteDescription')}
501
+ </AlertDialogDescription>
502
+ </AlertDialogHeader>
503
+ <AlertDialogFooter>
504
+ <AlertDialogCancel>{t('cancel')}</AlertDialogCancel>
505
+ <AlertDialogAction
506
+ onClick={handleDeleteConfirm}
507
+ className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
508
+ >
509
+ {t('delete')}
510
+ </AlertDialogAction>
511
+ </AlertDialogFooter>
512
+ </AlertDialogContent>
513
+ </AlertDialog>
514
+ </div>
515
+ );
516
+ }