rl-core-front 0.1.0

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 (112) hide show
  1. package/README.md +129 -0
  2. package/package.json +113 -0
  3. package/src/_services/api/axios.factory.ts +97 -0
  4. package/src/_services/api/list-query.ts +26 -0
  5. package/src/_services/api/schema.d.ts +2527 -0
  6. package/src/_services/auth/index.ts +127 -0
  7. package/src/_utils/advanced-filter.ts +386 -0
  8. package/src/_utils/calendar.ts +103 -0
  9. package/src/_utils/crop-image.ts +65 -0
  10. package/src/_utils/filter.ts +313 -0
  11. package/src/_utils/format.ts +35 -0
  12. package/src/_utils/initials.ts +24 -0
  13. package/src/_utils/password.ts +7 -0
  14. package/src/components/app-shell.tsx +314 -0
  15. package/src/components/breadcrumbs.tsx +55 -0
  16. package/src/components/index.ts +7 -0
  17. package/src/components/language-selector.tsx +51 -0
  18. package/src/components/password-field.tsx +40 -0
  19. package/src/components/theme-toggle.tsx +27 -0
  20. package/src/components/ui/alert.tsx +27 -0
  21. package/src/components/ui/avatar.tsx +50 -0
  22. package/src/components/ui/badge.tsx +36 -0
  23. package/src/components/ui/button.tsx +62 -0
  24. package/src/components/ui/card.tsx +86 -0
  25. package/src/components/ui/checkbox.tsx +30 -0
  26. package/src/components/ui/column-visibility-modal.tsx +110 -0
  27. package/src/components/ui/confirm-dialog.tsx +69 -0
  28. package/src/components/ui/data-table.tsx +401 -0
  29. package/src/components/ui/date-range-picker.tsx +250 -0
  30. package/src/components/ui/dialog.tsx +138 -0
  31. package/src/components/ui/dropdown-menu.tsx +108 -0
  32. package/src/components/ui/field.tsx +20 -0
  33. package/src/components/ui/filter-chips.tsx +88 -0
  34. package/src/components/ui/filter-field.tsx +137 -0
  35. package/src/components/ui/filter-group.tsx +155 -0
  36. package/src/components/ui/filter-rule.tsx +221 -0
  37. package/src/components/ui/filter-sheet.tsx +201 -0
  38. package/src/components/ui/index.ts +30 -0
  39. package/src/components/ui/input.tsx +25 -0
  40. package/src/components/ui/label.tsx +23 -0
  41. package/src/components/ui/row-actions.tsx +32 -0
  42. package/src/components/ui/select.tsx +97 -0
  43. package/src/components/ui/separator.tsx +31 -0
  44. package/src/components/ui/sheet.tsx +119 -0
  45. package/src/components/ui/sonner.tsx +28 -0
  46. package/src/components/ui/spinner.tsx +12 -0
  47. package/src/components/ui/switch.tsx +29 -0
  48. package/src/components/ui/table.tsx +84 -0
  49. package/src/components/ui/tabs.tsx +52 -0
  50. package/src/components/user-avatar.tsx +54 -0
  51. package/src/contexts/auth-context.tsx +90 -0
  52. package/src/contexts/color-mode-context.tsx +105 -0
  53. package/src/contexts/i18n-context.tsx +76 -0
  54. package/src/contexts/index.ts +6 -0
  55. package/src/contexts/socket-context.tsx +153 -0
  56. package/src/contexts/toast-context.tsx +40 -0
  57. package/src/features/audit/audit-screen.tsx +299 -0
  58. package/src/features/audit/hooks/use-audit-trail.ts +68 -0
  59. package/src/features/audit/services/audit.service.ts +24 -0
  60. package/src/features/dashboard/dashboard-screen.tsx +73 -0
  61. package/src/features/login/components/backup-codes-dialog.tsx +66 -0
  62. package/src/features/login/components/index.ts +5 -0
  63. package/src/features/login/components/login-form.tsx +62 -0
  64. package/src/features/login/components/two-factor-setup.tsx +71 -0
  65. package/src/features/login/components/two-factor-verify.tsx +56 -0
  66. package/src/features/login/hooks/use-login-flow.ts +119 -0
  67. package/src/features/login/login-screen.tsx +83 -0
  68. package/src/features/login/validation/schemas.ts +30 -0
  69. package/src/features/logs/hooks/use-error-logs.ts +65 -0
  70. package/src/features/logs/hooks/use-request-logs.ts +65 -0
  71. package/src/features/logs/logs-screen.tsx +31 -0
  72. package/src/features/logs/panels/error-logs-panel.tsx +183 -0
  73. package/src/features/logs/panels/index.ts +3 -0
  74. package/src/features/logs/panels/request-logs-panel.tsx +124 -0
  75. package/src/features/logs/services/logs.service.ts +68 -0
  76. package/src/features/notifications/components/notification-item.tsx +67 -0
  77. package/src/features/notifications/hooks/use-notifications.ts +78 -0
  78. package/src/features/notifications/notifications-center.tsx +119 -0
  79. package/src/features/notifications/services/notifications.service.ts +38 -0
  80. package/src/features/profile/components/avatar-crop-dialog.tsx +133 -0
  81. package/src/features/profile/components/index.ts +2 -0
  82. package/src/features/profile/force-password-change.tsx +114 -0
  83. package/src/features/profile/profile-screen.tsx +385 -0
  84. package/src/features/rbac/panels/actions-panel.tsx +20 -0
  85. package/src/features/rbac/panels/catalog-panel.tsx +243 -0
  86. package/src/features/rbac/panels/groups-panel.tsx +269 -0
  87. package/src/features/rbac/panels/index.ts +8 -0
  88. package/src/features/rbac/panels/permissions-panel.tsx +204 -0
  89. package/src/features/rbac/panels/resources-panel.tsx +20 -0
  90. package/src/features/rbac/panels/roles-panel.tsx +224 -0
  91. package/src/features/rbac/panels/scopes-panel.tsx +20 -0
  92. package/src/features/rbac/rbac-screen.tsx +80 -0
  93. package/src/features/rbac/services/rbac.service.ts +110 -0
  94. package/src/features/recovery/forgot-password-screen.tsx +69 -0
  95. package/src/features/recovery/reset-password-screen.tsx +163 -0
  96. package/src/features/users/components/user-form-dialog.tsx +187 -0
  97. package/src/features/users/hooks/use-users.ts +181 -0
  98. package/src/features/users/services/users.service.ts +83 -0
  99. package/src/features/users/users-screen.tsx +331 -0
  100. package/src/hooks/use-column-visibility.ts +54 -0
  101. package/src/hooks/use-confirm.tsx +70 -0
  102. package/src/hooks/use-filter-schema.ts +85 -0
  103. package/src/hooks/use-filters.ts +148 -0
  104. package/src/hooks/use-list-query.ts +69 -0
  105. package/src/hooks/use-request.ts +114 -0
  106. package/src/i18n/messages/en.ts +296 -0
  107. package/src/i18n/messages/pt.ts +305 -0
  108. package/src/index.ts +60 -0
  109. package/src/lib/utils.ts +6 -0
  110. package/src/middleware.ts +42 -0
  111. package/src/styles/core.css +142 -0
  112. package/tailwind-preset.ts +103 -0
@@ -0,0 +1,204 @@
1
+ "use client";
2
+
3
+ import type { ColumnDef } from "@tanstack/react-table";
4
+ import { Pencil, Plus, Trash2 } from "lucide-react";
5
+ import type { JSX } from "react";
6
+ import { useEffect, useMemo, useState } from "react";
7
+
8
+ import {
9
+ Button,
10
+ DataTable,
11
+ DataTableFeatures,
12
+ Dialog,
13
+ DialogContent,
14
+ DialogFooter,
15
+ DialogForm,
16
+ DialogHeader,
17
+ DialogTitle,
18
+ Field,
19
+ Input,
20
+ RowActions,
21
+ } from "#core/components/ui";
22
+ import { useAuth, useI18n } from "#core/contexts";
23
+ import { Permission, rbacService } from "#core/features/rbac/services/rbac.service";
24
+ import { useConfirm } from "#core/hooks/use-confirm";
25
+ import { RequestOperation, useRequest } from "#core/hooks/use-request";
26
+
27
+ export function PermissionsPanel(): JSX.Element {
28
+ const { run, loading } = useRequest();
29
+ const { t } = useI18n();
30
+ const { confirm, confirmDialog } = useConfirm();
31
+ const { hasPermission } = useAuth();
32
+ const [rows, setRows] = useState<Permission[]>([]);
33
+ const [open, setOpen] = useState(false);
34
+ const [editing, setEditing] = useState<Permission | null>(null);
35
+ const [form, setForm] = useState({ code: "", description: "" });
36
+
37
+ const load = async (): Promise<void> => {
38
+ const r = await run(() => rbacService.listPermissions());
39
+ if (r) {setRows(r);}
40
+ };
41
+ useEffect(() => {
42
+ void load();
43
+ // eslint-disable-next-line react-hooks/exhaustive-deps
44
+ }, []);
45
+
46
+ const openNew = (): void => {
47
+ setEditing(null);
48
+ setForm({ code: "", description: "" });
49
+ setOpen(true);
50
+ };
51
+ const openEdit = (p: Permission): void => {
52
+ setEditing(p);
53
+ setForm({ code: p.code, description: p.description ?? "" });
54
+ setOpen(true);
55
+ };
56
+
57
+ const save = async (): Promise<void> => {
58
+ const res = editing
59
+ ? await run(() => rbacService.updatePermission(editing.id, form), {
60
+ success: RequestOperation.Update,
61
+ })
62
+ : await run(() => rbacService.createPermission(form), {
63
+ success: RequestOperation.Create,
64
+ });
65
+ if (res) {
66
+ setOpen(false);
67
+ await load();
68
+ }
69
+ };
70
+
71
+ const remove = async (p: Permission): Promise<void> => {
72
+ const ok = await confirm({
73
+ title: t("common.delete"),
74
+ description: p.code,
75
+ confirmLabel: t("common.delete"),
76
+ destructive: true,
77
+ });
78
+ if (!ok) {
79
+ return;
80
+ }
81
+ const res = await run(() => rbacService.deletePermission(p.id), {
82
+ success: RequestOperation.Delete,
83
+ });
84
+ if (res !== null) {
85
+ await load();
86
+ }
87
+ };
88
+
89
+ const canManage =
90
+ hasPermission("permissions:update:any") ||
91
+ hasPermission("permissions:delete:any");
92
+
93
+ const columns = useMemo<
94
+ ColumnDef<DataTableFeatures, Permission, unknown>[]
95
+ >(() => {
96
+ const cols: ColumnDef<DataTableFeatures, Permission, unknown>[] = [
97
+ {
98
+ accessorKey: "code",
99
+ header: "Code",
100
+ meta: { label: "Code" },
101
+ cell: ({ getValue }) => (
102
+ <span className="font-mono text-xs">{String(getValue())}</span>
103
+ ),
104
+ },
105
+ {
106
+ accessorKey: "description",
107
+ header: "Descrição",
108
+ meta: { label: "Descrição" },
109
+ },
110
+ ];
111
+ if (canManage) {
112
+ cols.push({
113
+ id: "actions",
114
+ header: () => <div className="text-center">{t("common.actions")}</div>,
115
+ meta: { shrink: true },
116
+ enableHiding: false,
117
+ enableSorting: false,
118
+ cell: ({ row }) => (
119
+ <RowActions>
120
+ {hasPermission("permissions:update:any") && (
121
+ <Button
122
+ variant="ghost"
123
+ size="iconSm"
124
+ onClick={() => openEdit(row.original)}
125
+ >
126
+ <Pencil className="h-4 w-4" />
127
+ </Button>
128
+ )}
129
+ {hasPermission("permissions:delete:any") && (
130
+ <Button
131
+ variant="ghost"
132
+ size="iconSm"
133
+ className="text-destructive"
134
+ onClick={() => remove(row.original)}
135
+ >
136
+ <Trash2 className="h-4 w-4" />
137
+ </Button>
138
+ )}
139
+ </RowActions>
140
+ ),
141
+ });
142
+ }
143
+ return cols;
144
+ // eslint-disable-next-line react-hooks/exhaustive-deps
145
+ }, [canManage]);
146
+
147
+ return (
148
+ <div>
149
+ <DataTable
150
+ columns={columns}
151
+ data={rows}
152
+ getRowId={(r) => r.id}
153
+ storageKey="rbac-permissions"
154
+ loading={loading}
155
+ pageSizeOptions={[20, 50]}
156
+ toolbar={
157
+ hasPermission("permissions:create:any") && (
158
+ <Button onClick={openNew}>
159
+ <Plus className="h-4 w-4" />
160
+ {t("common.create")}
161
+ </Button>
162
+ )
163
+ }
164
+ />
165
+
166
+ <Dialog open={open} onOpenChange={(o) => !o && setOpen(false)}>
167
+ <DialogContent className="max-w-md">
168
+ <DialogHeader>
169
+ <DialogTitle>
170
+ {editing ? t("common.edit") : t("common.create")}
171
+ </DialogTitle>
172
+ </DialogHeader>
173
+ <DialogForm onSubmit={() => void save()}>
174
+ <Field label="Code (resource:action:scope)">
175
+ <Input
176
+ placeholder="users:read:any"
177
+ value={form.code}
178
+ onChange={(e) => setForm({ ...form, code: e.target.value })}
179
+ />
180
+ </Field>
181
+ <Field label="Descrição">
182
+ <Input
183
+ value={form.description}
184
+ onChange={(e) =>
185
+ setForm({ ...form, description: e.target.value })
186
+ }
187
+ />
188
+ </Field>
189
+ <DialogFooter className="gap-2">
190
+ <Button type="button" variant="outline" onClick={() => setOpen(false)}>
191
+ {t("common.cancel")}
192
+ </Button>
193
+ <Button type="submit" disabled={loading}>
194
+ {t("common.save")}
195
+ </Button>
196
+ </DialogFooter>
197
+ </DialogForm>
198
+ </DialogContent>
199
+ </Dialog>
200
+
201
+ {confirmDialog}
202
+ </div>
203
+ );
204
+ }
@@ -0,0 +1,20 @@
1
+ "use client";
2
+
3
+ import type { JSX } from "react";
4
+
5
+ import { CatalogPanel } from "#core/features/rbac/panels/catalog-panel";
6
+ import { rbacService } from "#core/features/rbac/services/rbac.service";
7
+
8
+ export function ResourcesPanel(): JSX.Element {
9
+ return (
10
+ <CatalogPanel
11
+ resource="resources"
12
+ storageKey="rbac-resources"
13
+ namePlaceholder="users"
14
+ list={rbacService.listResources}
15
+ create={rbacService.createResource}
16
+ update={rbacService.updateResource}
17
+ remove={rbacService.deleteResource}
18
+ />
19
+ );
20
+ }
@@ -0,0 +1,224 @@
1
+ "use client";
2
+
3
+ import { Pencil, Plus, Trash2 } from "lucide-react";
4
+ import type { JSX } from "react";
5
+ import { useEffect, useState } from "react";
6
+
7
+ import {
8
+ Badge,
9
+ Button,
10
+ Card,
11
+ CardContent,
12
+ Checkbox,
13
+ Dialog,
14
+ DialogContent,
15
+ DialogFooter,
16
+ DialogForm,
17
+ DialogHeader,
18
+ DialogTitle,
19
+ Field,
20
+ Input,
21
+ Label,
22
+ } from "#core/components/ui";
23
+ import { useAuth, useI18n } from "#core/contexts";
24
+ import {
25
+ Permission,
26
+ rbacService,
27
+ Role,
28
+ } from "#core/features/rbac/services/rbac.service";
29
+ import { useConfirm } from "#core/hooks/use-confirm";
30
+ import { RequestOperation, useRequest } from "#core/hooks/use-request";
31
+
32
+ export function RolesPanel(): JSX.Element {
33
+ const { run, loading } = useRequest();
34
+ const { t } = useI18n();
35
+ const { confirm, confirmDialog } = useConfirm();
36
+ const { hasPermission } = useAuth();
37
+ const [roles, setRoles] = useState<Role[]>([]);
38
+ const [perms, setPerms] = useState<Permission[]>([]);
39
+ const [open, setOpen] = useState(false);
40
+ const [editing, setEditing] = useState<Role | null>(null);
41
+ const [form, setForm] = useState({
42
+ name: "",
43
+ description: "",
44
+ permissionIds: [] as string[],
45
+ });
46
+
47
+ const load = async (): Promise<void> => {
48
+ const [r, p] = await Promise.all([
49
+ run(() => rbacService.listRoles()),
50
+ run(() => rbacService.listPermissions()),
51
+ ]);
52
+ if (r) {setRoles(r);}
53
+ if (p) {setPerms(p);}
54
+ };
55
+ useEffect(() => {
56
+ void load();
57
+ // eslint-disable-next-line react-hooks/exhaustive-deps
58
+ }, []);
59
+
60
+ const openNew = (): void => {
61
+ setEditing(null);
62
+ setForm({ name: "", description: "", permissionIds: [] });
63
+ setOpen(true);
64
+ };
65
+ const openEdit = (role: Role): void => {
66
+ setEditing(role);
67
+ setForm({
68
+ name: role.name,
69
+ description: role.description ?? "",
70
+ permissionIds: role.permissions.map((p) => p.id),
71
+ });
72
+ setOpen(true);
73
+ };
74
+
75
+ const save = async (): Promise<void> => {
76
+ const res = editing
77
+ ? await run(() => rbacService.updateRole(editing.id, form), {
78
+ success: RequestOperation.Update,
79
+ })
80
+ : await run(() => rbacService.createRole(form), {
81
+ success: RequestOperation.Create,
82
+ });
83
+ if (res) {
84
+ setOpen(false);
85
+ await load();
86
+ }
87
+ };
88
+
89
+ const remove = async (role: Role): Promise<void> => {
90
+ const ok = await confirm({
91
+ title: t("common.delete"),
92
+ description: role.name,
93
+ confirmLabel: t("common.delete"),
94
+ destructive: true,
95
+ });
96
+ if (!ok) {
97
+ return;
98
+ }
99
+ const res = await run(() => rbacService.deleteRole(role.id), {
100
+ success: RequestOperation.Delete,
101
+ });
102
+ if (res !== null) {
103
+ await load();
104
+ }
105
+ };
106
+
107
+ const canManage =
108
+ hasPermission("roles:update:any") || hasPermission("roles:delete:any");
109
+ const togglePerm = (id: string): void =>
110
+ setForm((f) => ({
111
+ ...f,
112
+ permissionIds: f.permissionIds.includes(id)
113
+ ? f.permissionIds.filter((x) => x !== id)
114
+ : [...f.permissionIds, id],
115
+ }));
116
+
117
+ return (
118
+ <div>
119
+ {hasPermission("roles:create:any") && (
120
+ <Button className="mb-4" onClick={openNew}>
121
+ <Plus className="h-4 w-4" />
122
+ {t("common.create")}
123
+ </Button>
124
+ )}
125
+ <div className="grid grid-cols-1 gap-4 md:grid-cols-2">
126
+ {roles.map((role) => (
127
+ <Card key={role.id}>
128
+ <CardContent className="pt-6">
129
+ <div className="flex items-start justify-between">
130
+ <h3 className="text-lg font-semibold">{role.name}</h3>
131
+ {canManage && (
132
+ <div className="flex">
133
+ {hasPermission("roles:update:any") && (
134
+ <Button
135
+ variant="ghost"
136
+ size="icon"
137
+ onClick={() => openEdit(role)}
138
+ >
139
+ <Pencil className="h-4 w-4" />
140
+ </Button>
141
+ )}
142
+ {hasPermission("roles:delete:any") && (
143
+ <Button
144
+ variant="ghost"
145
+ size="icon"
146
+ className="text-destructive"
147
+ onClick={() => remove(role)}
148
+ >
149
+ <Trash2 className="h-4 w-4" />
150
+ </Button>
151
+ )}
152
+ </div>
153
+ )}
154
+ </div>
155
+ <p className="mb-2 text-sm text-muted-foreground">
156
+ {role.description}
157
+ </p>
158
+ <div className="flex flex-wrap gap-1">
159
+ {role.permissions.map((p) => (
160
+ <Badge key={p.id} variant="outline">
161
+ {p.code}
162
+ </Badge>
163
+ ))}
164
+ </div>
165
+ </CardContent>
166
+ </Card>
167
+ ))}
168
+ </div>
169
+
170
+ <Dialog open={open} onOpenChange={(o) => !o && setOpen(false)}>
171
+ <DialogContent className="max-w-lg">
172
+ <DialogHeader>
173
+ <DialogTitle>
174
+ {editing ? t("common.edit") : t("common.create")}
175
+ </DialogTitle>
176
+ </DialogHeader>
177
+ <DialogForm onSubmit={() => void save()}>
178
+ <Field label={t("users.name")}>
179
+ <Input
180
+ value={form.name}
181
+ onChange={(e) => setForm({ ...form, name: e.target.value })}
182
+ />
183
+ </Field>
184
+ <Field label="Descrição">
185
+ <Input
186
+ value={form.description}
187
+ onChange={(e) =>
188
+ setForm({ ...form, description: e.target.value })
189
+ }
190
+ />
191
+ </Field>
192
+ <div className="space-y-2">
193
+ <Label>{t("dashboard.permissions")}</Label>
194
+ <div className="flex max-h-60 flex-col gap-2 overflow-y-auto rounded-md border border-border p-3">
195
+ {perms.map((p) => (
196
+ <label
197
+ key={p.id}
198
+ className="flex cursor-pointer items-center gap-2 text-sm"
199
+ >
200
+ <Checkbox
201
+ checked={form.permissionIds.includes(p.id)}
202
+ onCheckedChange={() => togglePerm(p.id)}
203
+ />
204
+ {p.code}
205
+ </label>
206
+ ))}
207
+ </div>
208
+ </div>
209
+ <DialogFooter className="gap-2">
210
+ <Button type="button" variant="outline" onClick={() => setOpen(false)}>
211
+ {t("common.cancel")}
212
+ </Button>
213
+ <Button type="submit" disabled={loading}>
214
+ {t("common.save")}
215
+ </Button>
216
+ </DialogFooter>
217
+ </DialogForm>
218
+ </DialogContent>
219
+ </Dialog>
220
+
221
+ {confirmDialog}
222
+ </div>
223
+ );
224
+ }
@@ -0,0 +1,20 @@
1
+ "use client";
2
+
3
+ import type { JSX } from "react";
4
+
5
+ import { CatalogPanel } from "#core/features/rbac/panels/catalog-panel";
6
+ import { rbacService } from "#core/features/rbac/services/rbac.service";
7
+
8
+ export function ScopesPanel(): JSX.Element {
9
+ return (
10
+ <CatalogPanel
11
+ resource="scopes"
12
+ storageKey="rbac-scopes"
13
+ namePlaceholder="own"
14
+ list={rbacService.listScopes}
15
+ create={rbacService.createScope}
16
+ update={rbacService.updateScope}
17
+ remove={rbacService.deleteScope}
18
+ />
19
+ );
20
+ }
@@ -0,0 +1,80 @@
1
+ "use client";
2
+
3
+ import type { JSX } from "react";
4
+
5
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from "#core/components/ui";
6
+ import { useAuth, useI18n } from "#core/contexts";
7
+ import {
8
+ ActionsPanel,
9
+ GroupsPanel,
10
+ PermissionsPanel,
11
+ ResourcesPanel,
12
+ RolesPanel,
13
+ ScopesPanel,
14
+ } from "#core/features/rbac/panels";
15
+
16
+ export function RbacScreen(): JSX.Element {
17
+ const { t } = useI18n();
18
+ const { hasPermission } = useAuth();
19
+
20
+ const tabs = [
21
+ {
22
+ value: "roles",
23
+ label: "Papéis",
24
+ perm: "roles:read:any",
25
+ panel: <RolesPanel />,
26
+ },
27
+ {
28
+ value: "permissions",
29
+ label: t("dashboard.permissions"),
30
+ perm: "permissions:read:any",
31
+ panel: <PermissionsPanel />,
32
+ },
33
+ {
34
+ value: "groups",
35
+ label: "Grupos",
36
+ perm: "groups:read:any",
37
+ panel: <GroupsPanel />,
38
+ },
39
+ {
40
+ value: "resources",
41
+ label: "Resources",
42
+ perm: "resources:read:any",
43
+ panel: <ResourcesPanel />,
44
+ },
45
+ {
46
+ value: "actions",
47
+ label: "Actions",
48
+ perm: "actions:read:any",
49
+ panel: <ActionsPanel />,
50
+ },
51
+ {
52
+ value: "scopes",
53
+ label: "Scopes",
54
+ perm: "scopes:read:any",
55
+ panel: <ScopesPanel />,
56
+ },
57
+ ].filter((x) => hasPermission(x.perm));
58
+
59
+ return (
60
+ <div>
61
+ <h1 className="mb-4 text-3xl font-bold">{t("rbac.title")}</h1>
62
+ {tabs.length > 0 && (
63
+ <Tabs defaultValue={tabs[0].value}>
64
+ <TabsList>
65
+ {tabs.map((x) => (
66
+ <TabsTrigger key={x.value} value={x.value}>
67
+ {x.label}
68
+ </TabsTrigger>
69
+ ))}
70
+ </TabsList>
71
+ {tabs.map((x) => (
72
+ <TabsContent key={x.value} value={x.value}>
73
+ {x.panel}
74
+ </TabsContent>
75
+ ))}
76
+ </Tabs>
77
+ )}
78
+ </div>
79
+ );
80
+ }
@@ -0,0 +1,110 @@
1
+ import { api } from "#core/_services/api/axios.factory";
2
+
3
+ export interface Permission {
4
+ id: string;
5
+ code: string;
6
+ description?: string | null;
7
+ isActive: boolean;
8
+ }
9
+ export interface Role {
10
+ id: string;
11
+ name: string;
12
+ description?: string | null;
13
+ isActive: boolean;
14
+ permissions: Permission[];
15
+ }
16
+ export interface Group {
17
+ id: string;
18
+ name: string;
19
+ description?: string | null;
20
+ type: string;
21
+ isActive: boolean;
22
+ roles?: Role[];
23
+ }
24
+ export interface RbacCatalogItem {
25
+ id: string;
26
+ name: string;
27
+ description?: string | null;
28
+ isActive: boolean;
29
+ }
30
+
31
+ export const rbacService = {
32
+ // Roles
33
+ listRoles: () => api.get<Role[]>("/rbac/roles").then((r) => r.data),
34
+ createRole: (data: {
35
+ name: string;
36
+ description?: string;
37
+ permissionIds?: string[];
38
+ }) => api.post<Role>("/rbac/roles", data).then((r) => r.data),
39
+ updateRole: (
40
+ id: string,
41
+ data: { name?: string; description?: string; permissionIds?: string[] },
42
+ ) => api.patch<Role>(`/rbac/roles/${id}`, data).then((r) => r.data),
43
+ deleteRole: (id: string) =>
44
+ api.delete(`/rbac/roles/${id}`).then((r) => r.data),
45
+
46
+ // Permissions
47
+ listPermissions: () =>
48
+ api.get<Permission[]>("/rbac/permissions").then((r) => r.data),
49
+ createPermission: (data: { code: string; description?: string }) =>
50
+ api.post<Permission>("/rbac/permissions", data).then((r) => r.data),
51
+ updatePermission: (
52
+ id: string,
53
+ data: { code?: string; description?: string },
54
+ ) =>
55
+ api.patch<Permission>(`/rbac/permissions/${id}`, data).then((r) => r.data),
56
+ deletePermission: (id: string) =>
57
+ api.delete(`/rbac/permissions/${id}`).then((r) => r.data),
58
+
59
+ // Groups
60
+ listGroups: () => api.get<Group[]>("/rbac/groups").then((r) => r.data),
61
+ createGroup: (data: {
62
+ name: string;
63
+ description?: string;
64
+ type?: string;
65
+ roleIds?: string[];
66
+ }) => api.post<Group>("/rbac/groups", data).then((r) => r.data),
67
+ updateGroup: (
68
+ id: string,
69
+ data: {
70
+ name?: string;
71
+ description?: string;
72
+ type?: string;
73
+ roleIds?: string[];
74
+ },
75
+ ) => api.patch<Group>(`/rbac/groups/${id}`, data).then((r) => r.data),
76
+ deleteGroup: (id: string) =>
77
+ api.delete(`/rbac/groups/${id}`).then((r) => r.data),
78
+
79
+ // Resources
80
+ listResources: () =>
81
+ api.get<RbacCatalogItem[]>("/rbac/resources").then((r) => r.data),
82
+ createResource: (data: { name: string; description?: string }) =>
83
+ api.post<RbacCatalogItem>("/rbac/resources", data).then((r) => r.data),
84
+ updateResource: (id: string, data: { name?: string; description?: string }) =>
85
+ api
86
+ .patch<RbacCatalogItem>(`/rbac/resources/${id}`, data)
87
+ .then((r) => r.data),
88
+ deleteResource: (id: string) =>
89
+ api.delete(`/rbac/resources/${id}`).then((r) => r.data),
90
+
91
+ // Actions
92
+ listActions: () =>
93
+ api.get<RbacCatalogItem[]>("/rbac/actions").then((r) => r.data),
94
+ createAction: (data: { name: string; description?: string }) =>
95
+ api.post<RbacCatalogItem>("/rbac/actions", data).then((r) => r.data),
96
+ updateAction: (id: string, data: { name?: string; description?: string }) =>
97
+ api.patch<RbacCatalogItem>(`/rbac/actions/${id}`, data).then((r) => r.data),
98
+ deleteAction: (id: string) =>
99
+ api.delete(`/rbac/actions/${id}`).then((r) => r.data),
100
+
101
+ // Scopes
102
+ listScopes: () =>
103
+ api.get<RbacCatalogItem[]>("/rbac/scopes").then((r) => r.data),
104
+ createScope: (data: { name: string; description?: string }) =>
105
+ api.post<RbacCatalogItem>("/rbac/scopes", data).then((r) => r.data),
106
+ updateScope: (id: string, data: { name?: string; description?: string }) =>
107
+ api.patch<RbacCatalogItem>(`/rbac/scopes/${id}`, data).then((r) => r.data),
108
+ deleteScope: (id: string) =>
109
+ api.delete(`/rbac/scopes/${id}`).then((r) => r.data),
110
+ };