rl-core-front 0.1.0 → 0.3.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.
- package/LICENSE +21 -0
- package/README.md +34 -5
- package/package.json +1 -1
- package/src/_services/api/schema.d.ts +243 -33
- package/src/components/app-shell.tsx +6 -6
- package/src/components/brand-header.tsx +35 -0
- package/src/components/index.ts +1 -0
- package/src/components/ui/data-table.tsx +12 -10
- package/src/contexts/brand-context.tsx +93 -0
- package/src/contexts/index.ts +1 -0
- package/src/features/login/login-screen.tsx +4 -8
- package/src/features/recovery/forgot-password-screen.tsx +2 -3
- package/src/features/recovery/reset-password-screen.tsx +2 -2
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Rodrigo Liberti
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -61,6 +61,7 @@ O `layout.tsx` empilha os providers:
|
|
|
61
61
|
```tsx
|
|
62
62
|
import {
|
|
63
63
|
AuthProvider,
|
|
64
|
+
BrandProvider,
|
|
64
65
|
ColorModeProvider,
|
|
65
66
|
I18nProvider,
|
|
66
67
|
SocketProvider,
|
|
@@ -71,17 +72,40 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
|
|
71
72
|
return (
|
|
72
73
|
<ColorModeProvider>
|
|
73
74
|
<I18nProvider>
|
|
74
|
-
<
|
|
75
|
-
<
|
|
76
|
-
<
|
|
77
|
-
|
|
78
|
-
|
|
75
|
+
<BrandProvider brand={{ name: "ControlX", mark: <Logo /> }}>
|
|
76
|
+
<ToastProvider>
|
|
77
|
+
<AuthProvider>
|
|
78
|
+
<SocketProvider>{children}</SocketProvider>
|
|
79
|
+
</AuthProvider>
|
|
80
|
+
</ToastProvider>
|
|
81
|
+
</BrandProvider>
|
|
79
82
|
</I18nProvider>
|
|
80
83
|
</ColorModeProvider>
|
|
81
84
|
);
|
|
82
85
|
}
|
|
83
86
|
```
|
|
84
87
|
|
|
88
|
+
## Identidade do sistema
|
|
89
|
+
|
|
90
|
+
O `BrandProvider` é o que troca o "Core App" pelo nome do projeto. Ele alimenta
|
|
91
|
+
o cabeçalho, o drawer e as telas de login, primeiro acesso e recuperação de
|
|
92
|
+
senha:
|
|
93
|
+
|
|
94
|
+
| Campo | O que é | Sem declarar |
|
|
95
|
+
|---|---|---|
|
|
96
|
+
| `name` | Nome do sistema | a chave `app.name` das traduções |
|
|
97
|
+
| `mark` | Marca compacta do cabeçalho (~24px) | o escudo do core |
|
|
98
|
+
| `logo` | Versão maior, para as telas de login | cai na `mark` |
|
|
99
|
+
| `tagline` | Frase curta sob a marca no login | não aparece |
|
|
100
|
+
|
|
101
|
+
`mark` e `logo` são `ReactNode`, não URL — use SVG inline, `next/image` ou um
|
|
102
|
+
ícone, o que o projeto preferir. O provider inteiro é opcional: sem ele, o
|
|
103
|
+
sistema segue com o padrão do core.
|
|
104
|
+
|
|
105
|
+
As cores continuam vindo do `tailwind-preset` e do `styles.css`, e o backend tem
|
|
106
|
+
a identidade dele no `.env` (`APP_NAME`, `APP_LOGO_URL`, `APP_BRAND_COLOR`) — é
|
|
107
|
+
de lá que sai a marca dos emails e o nome no app autenticador.
|
|
108
|
+
|
|
85
109
|
E o `middleware.ts` da raiz:
|
|
86
110
|
|
|
87
111
|
```ts
|
|
@@ -124,6 +148,11 @@ export const messages = {
|
|
|
124
148
|
`next`, `react`, `react-dom` e `tailwindcss` — precisam ser uma instância só.
|
|
125
149
|
O npm 7+ instala sozinho.
|
|
126
150
|
|
|
151
|
+
## Changelog
|
|
152
|
+
|
|
153
|
+
As três versões andam juntas e saem de uma tag só — o que mudou em cada uma
|
|
154
|
+
está em [CHANGELOG.md](https://github.com/liberti1991/aplication-core/blob/main/CHANGELOG.md).
|
|
155
|
+
|
|
127
156
|
## Licença
|
|
128
157
|
|
|
129
158
|
MIT © Rodrigo Liberti
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rl-core-front",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Telas e componentes Next.js do core: login com 2FA, usu\u00e1rios, RBAC, auditoria, logs e listagens com filtro din\u00e2mico",
|
|
5
5
|
"author": "Rodrigo Liberti",
|
|
6
6
|
"license": "MIT",
|
|
@@ -929,6 +929,20 @@ export interface components {
|
|
|
929
929
|
isActive: boolean;
|
|
930
930
|
roleIds?: string[];
|
|
931
931
|
};
|
|
932
|
+
ResourceResponse: {
|
|
933
|
+
/** Format: uuid */
|
|
934
|
+
id: string;
|
|
935
|
+
/** Format: date-time */
|
|
936
|
+
createdAt: string;
|
|
937
|
+
/** Format: date-time */
|
|
938
|
+
updatedAt: string;
|
|
939
|
+
/** Format: date-time */
|
|
940
|
+
deletedAt?: string | null;
|
|
941
|
+
/** @example users */
|
|
942
|
+
name: string;
|
|
943
|
+
description?: string | null;
|
|
944
|
+
isActive: boolean;
|
|
945
|
+
};
|
|
932
946
|
CreateResourceCommand: {
|
|
933
947
|
/** @example users */
|
|
934
948
|
name: string;
|
|
@@ -940,6 +954,20 @@ export interface components {
|
|
|
940
954
|
description?: string;
|
|
941
955
|
isActive?: boolean;
|
|
942
956
|
};
|
|
957
|
+
ActionResponse: {
|
|
958
|
+
/** Format: uuid */
|
|
959
|
+
id: string;
|
|
960
|
+
/** Format: date-time */
|
|
961
|
+
createdAt: string;
|
|
962
|
+
/** Format: date-time */
|
|
963
|
+
updatedAt: string;
|
|
964
|
+
/** Format: date-time */
|
|
965
|
+
deletedAt?: string | null;
|
|
966
|
+
/** @example read */
|
|
967
|
+
name: string;
|
|
968
|
+
description?: string | null;
|
|
969
|
+
isActive: boolean;
|
|
970
|
+
};
|
|
943
971
|
CreateActionCommand: {
|
|
944
972
|
/** @example read */
|
|
945
973
|
name: string;
|
|
@@ -951,6 +979,20 @@ export interface components {
|
|
|
951
979
|
description?: string;
|
|
952
980
|
isActive?: boolean;
|
|
953
981
|
};
|
|
982
|
+
ScopeResponse: {
|
|
983
|
+
/** Format: uuid */
|
|
984
|
+
id: string;
|
|
985
|
+
/** Format: date-time */
|
|
986
|
+
createdAt: string;
|
|
987
|
+
/** Format: date-time */
|
|
988
|
+
updatedAt: string;
|
|
989
|
+
/** Format: date-time */
|
|
990
|
+
deletedAt?: string | null;
|
|
991
|
+
/** @example any */
|
|
992
|
+
name: string;
|
|
993
|
+
description?: string | null;
|
|
994
|
+
isActive: boolean;
|
|
995
|
+
};
|
|
954
996
|
CreateScopeCommand: {
|
|
955
997
|
/**
|
|
956
998
|
* @description own | team | department | any
|
|
@@ -968,6 +1010,29 @@ export interface components {
|
|
|
968
1010
|
description?: string;
|
|
969
1011
|
isActive?: boolean;
|
|
970
1012
|
};
|
|
1013
|
+
PermissionResponse: {
|
|
1014
|
+
/** Format: uuid */
|
|
1015
|
+
id: string;
|
|
1016
|
+
/** Format: date-time */
|
|
1017
|
+
createdAt: string;
|
|
1018
|
+
/** Format: date-time */
|
|
1019
|
+
updatedAt: string;
|
|
1020
|
+
/** Format: date-time */
|
|
1021
|
+
deletedAt?: string | null;
|
|
1022
|
+
/** @example users:read:any */
|
|
1023
|
+
code: string;
|
|
1024
|
+
description?: string | null;
|
|
1025
|
+
isActive: boolean;
|
|
1026
|
+
/** Format: uuid */
|
|
1027
|
+
resourceId: string;
|
|
1028
|
+
/** Format: uuid */
|
|
1029
|
+
actionId: string;
|
|
1030
|
+
/** Format: uuid */
|
|
1031
|
+
scopeId: string;
|
|
1032
|
+
resource: components["schemas"]["ResourceResponse"];
|
|
1033
|
+
action: components["schemas"]["ActionResponse"];
|
|
1034
|
+
scope: components["schemas"]["ScopeResponse"];
|
|
1035
|
+
};
|
|
971
1036
|
CreatePermissionCommand: {
|
|
972
1037
|
/**
|
|
973
1038
|
* @description resource:action:scope
|
|
@@ -985,6 +1050,40 @@ export interface components {
|
|
|
985
1050
|
description?: string;
|
|
986
1051
|
isActive?: boolean;
|
|
987
1052
|
};
|
|
1053
|
+
/** @enum {string} */
|
|
1054
|
+
GroupType: "company" | "department" | "team";
|
|
1055
|
+
RoleResponse: {
|
|
1056
|
+
/** Format: uuid */
|
|
1057
|
+
id: string;
|
|
1058
|
+
/** Format: date-time */
|
|
1059
|
+
createdAt: string;
|
|
1060
|
+
/** Format: date-time */
|
|
1061
|
+
updatedAt: string;
|
|
1062
|
+
/** Format: date-time */
|
|
1063
|
+
deletedAt?: string | null;
|
|
1064
|
+
/** @example admin */
|
|
1065
|
+
name: string;
|
|
1066
|
+
description?: string | null;
|
|
1067
|
+
isActive: boolean;
|
|
1068
|
+
permissions?: components["schemas"]["PermissionResponse"][];
|
|
1069
|
+
};
|
|
1070
|
+
GroupResponse: {
|
|
1071
|
+
/** Format: uuid */
|
|
1072
|
+
id: string;
|
|
1073
|
+
/** Format: date-time */
|
|
1074
|
+
createdAt: string;
|
|
1075
|
+
/** Format: date-time */
|
|
1076
|
+
updatedAt: string;
|
|
1077
|
+
/** Format: date-time */
|
|
1078
|
+
deletedAt?: string | null;
|
|
1079
|
+
name: string;
|
|
1080
|
+
description?: string | null;
|
|
1081
|
+
type: components["schemas"]["GroupType"];
|
|
1082
|
+
/** Format: uuid */
|
|
1083
|
+
parentId?: string | null;
|
|
1084
|
+
isActive: boolean;
|
|
1085
|
+
roles?: components["schemas"]["RoleResponse"][];
|
|
1086
|
+
};
|
|
988
1087
|
CreateGroupCommand: {
|
|
989
1088
|
name: string;
|
|
990
1089
|
description?: string;
|
|
@@ -1023,8 +1122,6 @@ export interface components {
|
|
|
1023
1122
|
permissionIds?: string[];
|
|
1024
1123
|
isActive?: boolean;
|
|
1025
1124
|
};
|
|
1026
|
-
/** @enum {string} */
|
|
1027
|
-
AuditDataAction: "CREATE" | "UPDATE" | "DELETE";
|
|
1028
1125
|
AuditUserRefResponse: {
|
|
1029
1126
|
/** Format: uuid */
|
|
1030
1127
|
id: string;
|
|
@@ -1032,6 +1129,71 @@ export interface components {
|
|
|
1032
1129
|
/** @example admin@admin.com */
|
|
1033
1130
|
email: string;
|
|
1034
1131
|
};
|
|
1132
|
+
RequestLogResponse: {
|
|
1133
|
+
/** Format: uuid */
|
|
1134
|
+
id: string;
|
|
1135
|
+
/** @example GET /api/v1/users */
|
|
1136
|
+
action: string;
|
|
1137
|
+
/** @example User */
|
|
1138
|
+
entity?: string | null;
|
|
1139
|
+
entityId?: string | null;
|
|
1140
|
+
/** @example GET */
|
|
1141
|
+
method?: string | null;
|
|
1142
|
+
path?: string | null;
|
|
1143
|
+
/** @example 200 */
|
|
1144
|
+
statusCode?: number | null;
|
|
1145
|
+
/** @example 42 */
|
|
1146
|
+
durationMs?: number | null;
|
|
1147
|
+
/** @example 10.0.0.1 */
|
|
1148
|
+
ipAddress?: string | null;
|
|
1149
|
+
/** Format: uuid */
|
|
1150
|
+
requestId?: string | null;
|
|
1151
|
+
metadata?: Record<string, never> | null;
|
|
1152
|
+
userId?: string | null;
|
|
1153
|
+
user?: components["schemas"]["AuditUserRefResponse"] | null;
|
|
1154
|
+
/** Format: date-time */
|
|
1155
|
+
createdAt: string;
|
|
1156
|
+
/** Format: date-time */
|
|
1157
|
+
updatedAt: string;
|
|
1158
|
+
/** Format: date-time */
|
|
1159
|
+
deletedAt?: string | null;
|
|
1160
|
+
};
|
|
1161
|
+
PaginatedRequestLogsResponse: {
|
|
1162
|
+
items: components["schemas"]["RequestLogResponse"][];
|
|
1163
|
+
meta: components["schemas"]["PaginationMetaResponse"];
|
|
1164
|
+
};
|
|
1165
|
+
/** @enum {string} */
|
|
1166
|
+
ErrorType: "VALIDATION" | "AUTH" | "NOT_FOUND" | "RATE_LIMIT" | "DATABASE" | "INTEGRATION" | "INTERNAL";
|
|
1167
|
+
ErrorLogResponse: {
|
|
1168
|
+
/** Format: uuid */
|
|
1169
|
+
id: string;
|
|
1170
|
+
type: components["schemas"]["ErrorType"];
|
|
1171
|
+
/** @example 500 */
|
|
1172
|
+
statusCode?: number | null;
|
|
1173
|
+
message: string;
|
|
1174
|
+
stack?: string | null;
|
|
1175
|
+
/** @example POST */
|
|
1176
|
+
method?: string | null;
|
|
1177
|
+
path?: string | null;
|
|
1178
|
+
/** @example 10.0.0.1 */
|
|
1179
|
+
ipAddress?: string | null;
|
|
1180
|
+
/** Format: uuid */
|
|
1181
|
+
requestId?: string | null;
|
|
1182
|
+
userId?: string | null;
|
|
1183
|
+
user?: components["schemas"]["AuditUserRefResponse"] | null;
|
|
1184
|
+
/** Format: date-time */
|
|
1185
|
+
createdAt: string;
|
|
1186
|
+
/** Format: date-time */
|
|
1187
|
+
updatedAt: string;
|
|
1188
|
+
/** Format: date-time */
|
|
1189
|
+
deletedAt?: string | null;
|
|
1190
|
+
};
|
|
1191
|
+
PaginatedErrorLogsResponse: {
|
|
1192
|
+
items: components["schemas"]["ErrorLogResponse"][];
|
|
1193
|
+
meta: components["schemas"]["PaginationMetaResponse"];
|
|
1194
|
+
};
|
|
1195
|
+
/** @enum {string} */
|
|
1196
|
+
AuditDataAction: "CREATE" | "UPDATE" | "DELETE";
|
|
1035
1197
|
AuditDataChangeResponse: {
|
|
1036
1198
|
/** Format: uuid */
|
|
1037
1199
|
id: string;
|
|
@@ -1696,7 +1858,9 @@ export interface operations {
|
|
|
1696
1858
|
headers: {
|
|
1697
1859
|
[name: string]: unknown;
|
|
1698
1860
|
};
|
|
1699
|
-
content
|
|
1861
|
+
content: {
|
|
1862
|
+
"application/json": components["schemas"]["ResourceResponse"][];
|
|
1863
|
+
};
|
|
1700
1864
|
};
|
|
1701
1865
|
};
|
|
1702
1866
|
};
|
|
@@ -1713,11 +1877,13 @@ export interface operations {
|
|
|
1713
1877
|
};
|
|
1714
1878
|
};
|
|
1715
1879
|
responses: {
|
|
1716
|
-
|
|
1880
|
+
200: {
|
|
1717
1881
|
headers: {
|
|
1718
1882
|
[name: string]: unknown;
|
|
1719
1883
|
};
|
|
1720
|
-
content
|
|
1884
|
+
content: {
|
|
1885
|
+
"application/json": components["schemas"]["ResourceResponse"];
|
|
1886
|
+
};
|
|
1721
1887
|
};
|
|
1722
1888
|
};
|
|
1723
1889
|
};
|
|
@@ -1759,7 +1925,9 @@ export interface operations {
|
|
|
1759
1925
|
headers: {
|
|
1760
1926
|
[name: string]: unknown;
|
|
1761
1927
|
};
|
|
1762
|
-
content
|
|
1928
|
+
content: {
|
|
1929
|
+
"application/json": components["schemas"]["ResourceResponse"];
|
|
1930
|
+
};
|
|
1763
1931
|
};
|
|
1764
1932
|
};
|
|
1765
1933
|
};
|
|
@@ -1776,7 +1944,9 @@ export interface operations {
|
|
|
1776
1944
|
headers: {
|
|
1777
1945
|
[name: string]: unknown;
|
|
1778
1946
|
};
|
|
1779
|
-
content
|
|
1947
|
+
content: {
|
|
1948
|
+
"application/json": components["schemas"]["ActionResponse"][];
|
|
1949
|
+
};
|
|
1780
1950
|
};
|
|
1781
1951
|
};
|
|
1782
1952
|
};
|
|
@@ -1793,11 +1963,13 @@ export interface operations {
|
|
|
1793
1963
|
};
|
|
1794
1964
|
};
|
|
1795
1965
|
responses: {
|
|
1796
|
-
|
|
1966
|
+
200: {
|
|
1797
1967
|
headers: {
|
|
1798
1968
|
[name: string]: unknown;
|
|
1799
1969
|
};
|
|
1800
|
-
content
|
|
1970
|
+
content: {
|
|
1971
|
+
"application/json": components["schemas"]["ActionResponse"];
|
|
1972
|
+
};
|
|
1801
1973
|
};
|
|
1802
1974
|
};
|
|
1803
1975
|
};
|
|
@@ -1839,7 +2011,9 @@ export interface operations {
|
|
|
1839
2011
|
headers: {
|
|
1840
2012
|
[name: string]: unknown;
|
|
1841
2013
|
};
|
|
1842
|
-
content
|
|
2014
|
+
content: {
|
|
2015
|
+
"application/json": components["schemas"]["ActionResponse"];
|
|
2016
|
+
};
|
|
1843
2017
|
};
|
|
1844
2018
|
};
|
|
1845
2019
|
};
|
|
@@ -1856,7 +2030,9 @@ export interface operations {
|
|
|
1856
2030
|
headers: {
|
|
1857
2031
|
[name: string]: unknown;
|
|
1858
2032
|
};
|
|
1859
|
-
content
|
|
2033
|
+
content: {
|
|
2034
|
+
"application/json": components["schemas"]["ScopeResponse"][];
|
|
2035
|
+
};
|
|
1860
2036
|
};
|
|
1861
2037
|
};
|
|
1862
2038
|
};
|
|
@@ -1873,11 +2049,13 @@ export interface operations {
|
|
|
1873
2049
|
};
|
|
1874
2050
|
};
|
|
1875
2051
|
responses: {
|
|
1876
|
-
|
|
2052
|
+
200: {
|
|
1877
2053
|
headers: {
|
|
1878
2054
|
[name: string]: unknown;
|
|
1879
2055
|
};
|
|
1880
|
-
content
|
|
2056
|
+
content: {
|
|
2057
|
+
"application/json": components["schemas"]["ScopeResponse"];
|
|
2058
|
+
};
|
|
1881
2059
|
};
|
|
1882
2060
|
};
|
|
1883
2061
|
};
|
|
@@ -1919,7 +2097,9 @@ export interface operations {
|
|
|
1919
2097
|
headers: {
|
|
1920
2098
|
[name: string]: unknown;
|
|
1921
2099
|
};
|
|
1922
|
-
content
|
|
2100
|
+
content: {
|
|
2101
|
+
"application/json": components["schemas"]["ScopeResponse"];
|
|
2102
|
+
};
|
|
1923
2103
|
};
|
|
1924
2104
|
};
|
|
1925
2105
|
};
|
|
@@ -1936,7 +2116,9 @@ export interface operations {
|
|
|
1936
2116
|
headers: {
|
|
1937
2117
|
[name: string]: unknown;
|
|
1938
2118
|
};
|
|
1939
|
-
content
|
|
2119
|
+
content: {
|
|
2120
|
+
"application/json": components["schemas"]["PermissionResponse"][];
|
|
2121
|
+
};
|
|
1940
2122
|
};
|
|
1941
2123
|
};
|
|
1942
2124
|
};
|
|
@@ -1953,11 +2135,13 @@ export interface operations {
|
|
|
1953
2135
|
};
|
|
1954
2136
|
};
|
|
1955
2137
|
responses: {
|
|
1956
|
-
|
|
2138
|
+
200: {
|
|
1957
2139
|
headers: {
|
|
1958
2140
|
[name: string]: unknown;
|
|
1959
2141
|
};
|
|
1960
|
-
content
|
|
2142
|
+
content: {
|
|
2143
|
+
"application/json": components["schemas"]["PermissionResponse"];
|
|
2144
|
+
};
|
|
1961
2145
|
};
|
|
1962
2146
|
};
|
|
1963
2147
|
};
|
|
@@ -1999,7 +2183,9 @@ export interface operations {
|
|
|
1999
2183
|
headers: {
|
|
2000
2184
|
[name: string]: unknown;
|
|
2001
2185
|
};
|
|
2002
|
-
content
|
|
2186
|
+
content: {
|
|
2187
|
+
"application/json": components["schemas"]["PermissionResponse"];
|
|
2188
|
+
};
|
|
2003
2189
|
};
|
|
2004
2190
|
};
|
|
2005
2191
|
};
|
|
@@ -2016,7 +2202,9 @@ export interface operations {
|
|
|
2016
2202
|
headers: {
|
|
2017
2203
|
[name: string]: unknown;
|
|
2018
2204
|
};
|
|
2019
|
-
content
|
|
2205
|
+
content: {
|
|
2206
|
+
"application/json": components["schemas"]["GroupResponse"][];
|
|
2207
|
+
};
|
|
2020
2208
|
};
|
|
2021
2209
|
};
|
|
2022
2210
|
};
|
|
@@ -2033,11 +2221,13 @@ export interface operations {
|
|
|
2033
2221
|
};
|
|
2034
2222
|
};
|
|
2035
2223
|
responses: {
|
|
2036
|
-
|
|
2224
|
+
200: {
|
|
2037
2225
|
headers: {
|
|
2038
2226
|
[name: string]: unknown;
|
|
2039
2227
|
};
|
|
2040
|
-
content
|
|
2228
|
+
content: {
|
|
2229
|
+
"application/json": components["schemas"]["GroupResponse"];
|
|
2230
|
+
};
|
|
2041
2231
|
};
|
|
2042
2232
|
};
|
|
2043
2233
|
};
|
|
@@ -2056,7 +2246,9 @@ export interface operations {
|
|
|
2056
2246
|
headers: {
|
|
2057
2247
|
[name: string]: unknown;
|
|
2058
2248
|
};
|
|
2059
|
-
content
|
|
2249
|
+
content: {
|
|
2250
|
+
"application/json": components["schemas"]["GroupResponse"];
|
|
2251
|
+
};
|
|
2060
2252
|
};
|
|
2061
2253
|
};
|
|
2062
2254
|
};
|
|
@@ -2098,7 +2290,9 @@ export interface operations {
|
|
|
2098
2290
|
headers: {
|
|
2099
2291
|
[name: string]: unknown;
|
|
2100
2292
|
};
|
|
2101
|
-
content
|
|
2293
|
+
content: {
|
|
2294
|
+
"application/json": components["schemas"]["GroupResponse"];
|
|
2295
|
+
};
|
|
2102
2296
|
};
|
|
2103
2297
|
};
|
|
2104
2298
|
};
|
|
@@ -2155,7 +2349,9 @@ export interface operations {
|
|
|
2155
2349
|
headers: {
|
|
2156
2350
|
[name: string]: unknown;
|
|
2157
2351
|
};
|
|
2158
|
-
content
|
|
2352
|
+
content: {
|
|
2353
|
+
"application/json": components["schemas"]["RoleResponse"][];
|
|
2354
|
+
};
|
|
2159
2355
|
};
|
|
2160
2356
|
};
|
|
2161
2357
|
};
|
|
@@ -2172,11 +2368,13 @@ export interface operations {
|
|
|
2172
2368
|
};
|
|
2173
2369
|
};
|
|
2174
2370
|
responses: {
|
|
2175
|
-
|
|
2371
|
+
200: {
|
|
2176
2372
|
headers: {
|
|
2177
2373
|
[name: string]: unknown;
|
|
2178
2374
|
};
|
|
2179
|
-
content
|
|
2375
|
+
content: {
|
|
2376
|
+
"application/json": components["schemas"]["RoleResponse"];
|
|
2377
|
+
};
|
|
2180
2378
|
};
|
|
2181
2379
|
};
|
|
2182
2380
|
};
|
|
@@ -2195,7 +2393,9 @@ export interface operations {
|
|
|
2195
2393
|
headers: {
|
|
2196
2394
|
[name: string]: unknown;
|
|
2197
2395
|
};
|
|
2198
|
-
content
|
|
2396
|
+
content: {
|
|
2397
|
+
"application/json": components["schemas"]["RoleResponse"];
|
|
2398
|
+
};
|
|
2199
2399
|
};
|
|
2200
2400
|
};
|
|
2201
2401
|
};
|
|
@@ -2237,7 +2437,9 @@ export interface operations {
|
|
|
2237
2437
|
headers: {
|
|
2238
2438
|
[name: string]: unknown;
|
|
2239
2439
|
};
|
|
2240
|
-
content
|
|
2440
|
+
content: {
|
|
2441
|
+
"application/json": components["schemas"]["RoleResponse"];
|
|
2442
|
+
};
|
|
2241
2443
|
};
|
|
2242
2444
|
};
|
|
2243
2445
|
};
|
|
@@ -2253,11 +2455,13 @@ export interface operations {
|
|
|
2253
2455
|
};
|
|
2254
2456
|
requestBody?: never;
|
|
2255
2457
|
responses: {
|
|
2256
|
-
|
|
2458
|
+
200: {
|
|
2257
2459
|
headers: {
|
|
2258
2460
|
[name: string]: unknown;
|
|
2259
2461
|
};
|
|
2260
|
-
content
|
|
2462
|
+
content: {
|
|
2463
|
+
"application/json": components["schemas"]["RoleResponse"];
|
|
2464
|
+
};
|
|
2261
2465
|
};
|
|
2262
2466
|
};
|
|
2263
2467
|
};
|
|
@@ -2277,7 +2481,9 @@ export interface operations {
|
|
|
2277
2481
|
headers: {
|
|
2278
2482
|
[name: string]: unknown;
|
|
2279
2483
|
};
|
|
2280
|
-
content
|
|
2484
|
+
content: {
|
|
2485
|
+
"application/json": components["schemas"]["RoleResponse"];
|
|
2486
|
+
};
|
|
2281
2487
|
};
|
|
2282
2488
|
};
|
|
2283
2489
|
};
|
|
@@ -2303,7 +2509,9 @@ export interface operations {
|
|
|
2303
2509
|
headers: {
|
|
2304
2510
|
[name: string]: unknown;
|
|
2305
2511
|
};
|
|
2306
|
-
content
|
|
2512
|
+
content: {
|
|
2513
|
+
"application/json": components["schemas"]["PaginatedRequestLogsResponse"];
|
|
2514
|
+
};
|
|
2307
2515
|
};
|
|
2308
2516
|
};
|
|
2309
2517
|
};
|
|
@@ -2348,7 +2556,9 @@ export interface operations {
|
|
|
2348
2556
|
headers: {
|
|
2349
2557
|
[name: string]: unknown;
|
|
2350
2558
|
};
|
|
2351
|
-
content
|
|
2559
|
+
content: {
|
|
2560
|
+
"application/json": components["schemas"]["PaginatedErrorLogsResponse"];
|
|
2561
|
+
};
|
|
2352
2562
|
};
|
|
2353
2563
|
};
|
|
2354
2564
|
};
|
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
Menu,
|
|
10
10
|
ScrollText,
|
|
11
11
|
Settings,
|
|
12
|
-
Shield,
|
|
13
12
|
ShieldCheck,
|
|
14
13
|
User,
|
|
15
14
|
Users,
|
|
@@ -33,7 +32,7 @@ import {
|
|
|
33
32
|
Spinner,
|
|
34
33
|
} from "#core/components/ui";
|
|
35
34
|
import { UserAvatar } from "#core/components/user-avatar";
|
|
36
|
-
import { useAuth, useI18n } from "#core/contexts";
|
|
35
|
+
import { useAuth, useBrand, useI18n } from "#core/contexts";
|
|
37
36
|
import { NotificationsCenter } from "#core/features/notifications/notifications-center";
|
|
38
37
|
import { ForcePasswordChange } from "#core/features/profile/force-password-change";
|
|
39
38
|
import { cn } from "#core/lib/utils";
|
|
@@ -75,6 +74,7 @@ const ADMIN_CHILDREN: NavItem[] = [
|
|
|
75
74
|
export function AppShell({ children }: { children: React.ReactNode }): JSX.Element {
|
|
76
75
|
const { user, loading, logout, hasPermission } = useAuth();
|
|
77
76
|
const { t } = useI18n();
|
|
77
|
+
const brand = useBrand();
|
|
78
78
|
const router = useRouter();
|
|
79
79
|
const pathname = usePathname();
|
|
80
80
|
|
|
@@ -221,9 +221,9 @@ export function AppShell({ children }: { children: React.ReactNode }): JSX.Eleme
|
|
|
221
221
|
</Button>
|
|
222
222
|
|
|
223
223
|
<div className="flex items-center gap-2">
|
|
224
|
-
|
|
224
|
+
{brand.mark}
|
|
225
225
|
<span className="hidden text-lg font-extrabold sm:block">
|
|
226
|
-
{
|
|
226
|
+
{brand.name}
|
|
227
227
|
</span>
|
|
228
228
|
</div>
|
|
229
229
|
|
|
@@ -289,8 +289,8 @@ export function AppShell({ children }: { children: React.ReactNode }): JSX.Eleme
|
|
|
289
289
|
/>
|
|
290
290
|
<aside className="absolute left-0 top-0 flex h-full w-64 flex-col border-r border-border bg-card">
|
|
291
291
|
<div className="flex h-16 items-center gap-2 border-b border-border px-4">
|
|
292
|
-
|
|
293
|
-
<span className="text-lg font-extrabold">{
|
|
292
|
+
{brand.mark}
|
|
293
|
+
<span className="text-lg font-extrabold">{brand.name}</span>
|
|
294
294
|
</div>
|
|
295
295
|
<NavContent mini={false} />
|
|
296
296
|
</aside>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { JSX } from "react";
|
|
4
|
+
|
|
5
|
+
import { useBrand } from "#core/contexts";
|
|
6
|
+
|
|
7
|
+
export interface BrandHeaderProps {
|
|
8
|
+
/** Título do passo ou da tela, exibido sob a marca. */
|
|
9
|
+
title: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Cabeçalho das telas de autenticação: logo, nome do sistema e o título da vez.
|
|
14
|
+
*
|
|
15
|
+
* Existe porque login, primeiro acesso e recuperação de senha são as três telas
|
|
16
|
+
* que uma pessoa vê antes de entrar — e as três precisam dizer em que sistema
|
|
17
|
+
* ela está. O conteúdo vem do `BrandProvider` do projeto; sem ele, do padrão do
|
|
18
|
+
* core.
|
|
19
|
+
*/
|
|
20
|
+
export function BrandHeader({ title }: BrandHeaderProps): JSX.Element {
|
|
21
|
+
const brand = useBrand();
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<div className="mb-6 flex flex-col items-center gap-1">
|
|
25
|
+
<div className="mb-2 flex h-12 w-12 items-center justify-center rounded-full bg-primary/10">
|
|
26
|
+
{brand.logo}
|
|
27
|
+
</div>
|
|
28
|
+
<span className="text-lg font-extrabold">{brand.name}</span>
|
|
29
|
+
{brand.tagline && (
|
|
30
|
+
<p className="text-sm text-muted-foreground">{brand.tagline}</p>
|
|
31
|
+
)}
|
|
32
|
+
<h1 className="mt-2 text-xl font-semibold">{title}</h1>
|
|
33
|
+
</div>
|
|
34
|
+
);
|
|
35
|
+
}
|
package/src/components/index.ts
CHANGED
|
@@ -227,16 +227,8 @@ export function DataTable<T extends RowData>({
|
|
|
227
227
|
<div className="mb-3 flex items-center justify-between gap-2">
|
|
228
228
|
<div className="flex items-center gap-2">{toolbar}</div>
|
|
229
229
|
<div className="flex items-center gap-2">
|
|
230
|
-
{
|
|
231
|
-
|
|
232
|
-
variant="outline"
|
|
233
|
-
size="sm"
|
|
234
|
-
onClick={() => setColsOpen(true)}
|
|
235
|
-
>
|
|
236
|
-
<SlidersHorizontal className="h-4 w-4" />
|
|
237
|
-
{t("table.columnsButton")}
|
|
238
|
-
</Button>
|
|
239
|
-
)}
|
|
230
|
+
{/* Filtro antes de colunas: filtrar é a ação do dia a dia, escolher
|
|
231
|
+
coluna se faz uma vez e a tela guarda. */}
|
|
240
232
|
{filtersEnabled && (
|
|
241
233
|
<Button
|
|
242
234
|
variant="outline"
|
|
@@ -252,6 +244,16 @@ export function DataTable<T extends RowData>({
|
|
|
252
244
|
)}
|
|
253
245
|
</Button>
|
|
254
246
|
)}
|
|
247
|
+
{storageKey && hideable.length > 0 && (
|
|
248
|
+
<Button
|
|
249
|
+
variant="outline"
|
|
250
|
+
size="sm"
|
|
251
|
+
onClick={() => setColsOpen(true)}
|
|
252
|
+
>
|
|
253
|
+
<SlidersHorizontal className="h-4 w-4" />
|
|
254
|
+
{t("table.columnsButton")}
|
|
255
|
+
</Button>
|
|
256
|
+
)}
|
|
255
257
|
</div>
|
|
256
258
|
</div>
|
|
257
259
|
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Shield } from "lucide-react";
|
|
4
|
+
import type { JSX, ReactNode } from "react";
|
|
5
|
+
import { createContext, useContext, useMemo } from "react";
|
|
6
|
+
|
|
7
|
+
import { useI18n } from "#core/contexts/i18n-context";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Identidade visual do sistema.
|
|
11
|
+
*
|
|
12
|
+
* O core não tem nome nem logo próprios: quem se chama de alguma coisa é o
|
|
13
|
+
* projeto que o consome. Declarar é opcional — sem `BrandProvider`, o shell e o
|
|
14
|
+
* login seguem com o escudo e o `app.name` das traduções, que é o que já
|
|
15
|
+
* acontecia antes desta configuração existir.
|
|
16
|
+
*
|
|
17
|
+
* A logo entra como `ReactNode` e não como URL de propósito: assim o projeto
|
|
18
|
+
* usa o que quiser (SVG inline, `next/image`, um ícone da lucide) sem que o
|
|
19
|
+
* core precise saber de onde o arquivo vem.
|
|
20
|
+
*/
|
|
21
|
+
export interface Brand {
|
|
22
|
+
/** Nome do sistema. Sem isto vale a chave `app.name` das traduções. */
|
|
23
|
+
name?: string;
|
|
24
|
+
/**
|
|
25
|
+
* Marca compacta, exibida ao lado do nome no cabeçalho e no drawer.
|
|
26
|
+
* Deve caber num quadrado de ~24px. Sem isto vale o escudo do core.
|
|
27
|
+
*/
|
|
28
|
+
mark?: ReactNode;
|
|
29
|
+
/**
|
|
30
|
+
* Versão maior, para as telas de login, primeiro acesso e recuperação de
|
|
31
|
+
* senha. Sem isto a `mark` é usada nos dois lugares.
|
|
32
|
+
*/
|
|
33
|
+
logo?: ReactNode;
|
|
34
|
+
/** Frase curta sob a logo na tela de login. Opcional. */
|
|
35
|
+
tagline?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** O que os componentes do core consomem: sem opcionais, tudo já resolvido. */
|
|
39
|
+
export interface BrandContextType {
|
|
40
|
+
name: string;
|
|
41
|
+
mark: ReactNode;
|
|
42
|
+
logo: ReactNode;
|
|
43
|
+
tagline: string | null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const BrandContext = createContext<BrandContextType | undefined>(undefined);
|
|
47
|
+
|
|
48
|
+
/** Marca do core: é o que aparece enquanto o projeto não declara a dele. */
|
|
49
|
+
const defaultMark = <Shield className="h-6 w-6 text-primary" />;
|
|
50
|
+
|
|
51
|
+
export function BrandProvider({
|
|
52
|
+
brand,
|
|
53
|
+
children,
|
|
54
|
+
}: {
|
|
55
|
+
brand?: Brand;
|
|
56
|
+
children: ReactNode;
|
|
57
|
+
}): JSX.Element {
|
|
58
|
+
const { t } = useI18n();
|
|
59
|
+
|
|
60
|
+
const value = useMemo<BrandContextType>(() => {
|
|
61
|
+
const mark = brand?.mark ?? defaultMark;
|
|
62
|
+
return {
|
|
63
|
+
name: brand?.name ?? t("app.name"),
|
|
64
|
+
mark,
|
|
65
|
+
logo: brand?.logo ?? mark,
|
|
66
|
+
tagline: brand?.tagline ?? null,
|
|
67
|
+
};
|
|
68
|
+
}, [brand, t]);
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<BrandContext.Provider value={value}>{children}</BrandContext.Provider>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Identidade em vigor.
|
|
77
|
+
*
|
|
78
|
+
* Funciona mesmo sem `BrandProvider` no topo: cai no padrão do core em vez de
|
|
79
|
+
* quebrar, para que um projeto que ainda não declarou marca continue subindo.
|
|
80
|
+
*/
|
|
81
|
+
export function useBrand(): BrandContextType {
|
|
82
|
+
const ctx = useContext(BrandContext);
|
|
83
|
+
const { t } = useI18n();
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
ctx ?? {
|
|
87
|
+
name: t("app.name"),
|
|
88
|
+
mark: defaultMark,
|
|
89
|
+
logo: defaultMark,
|
|
90
|
+
tagline: null,
|
|
91
|
+
}
|
|
92
|
+
);
|
|
93
|
+
}
|
package/src/contexts/index.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { Lock } from "lucide-react";
|
|
4
3
|
import type { JSX } from "react";
|
|
5
4
|
import { useEffect } from "react";
|
|
6
5
|
|
|
7
|
-
import { LanguageSelector, ThemeToggle } from "#core/components";
|
|
6
|
+
import { BrandHeader, LanguageSelector, ThemeToggle } from "#core/components";
|
|
8
7
|
import { Card } from "#core/components/ui";
|
|
9
8
|
import { useI18n } from "#core/contexts";
|
|
10
9
|
import {
|
|
@@ -39,12 +38,9 @@ export function LoginScreen(): JSX.Element {
|
|
|
39
38
|
</div>
|
|
40
39
|
|
|
41
40
|
<Card className="w-full max-w-md p-8">
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
</div>
|
|
46
|
-
<h1 className="text-xl font-semibold">{titles[flow.step]}</h1>
|
|
47
|
-
</div>
|
|
41
|
+
{/* A marca vem antes do título do passo: é a primeira tela do sistema,
|
|
42
|
+
e quem chega aqui precisa reconhecer onde está antes de logar. */}
|
|
43
|
+
<BrandHeader title={titles[flow.step]} />
|
|
48
44
|
|
|
49
45
|
{flow.error && (
|
|
50
46
|
<div className="mb-4 rounded-md border border-destructive/30 bg-destructive/10 px-3 py-2 text-sm text-destructive">
|
|
@@ -8,6 +8,7 @@ import { useForm } from "react-hook-form";
|
|
|
8
8
|
import * as yup from "yup";
|
|
9
9
|
|
|
10
10
|
import { authService } from "#core/_services/auth";
|
|
11
|
+
import { BrandHeader } from "#core/components";
|
|
11
12
|
import { Alert, Button, Card, Field, Input } from "#core/components/ui";
|
|
12
13
|
import { useI18n } from "#core/contexts";
|
|
13
14
|
import { useRequest } from "#core/hooks/use-request";
|
|
@@ -36,9 +37,7 @@ export function ForgotPasswordScreen(): JSX.Element {
|
|
|
36
37
|
return (
|
|
37
38
|
<div className="flex min-h-screen items-center justify-center bg-background p-4">
|
|
38
39
|
<Card className="w-full max-w-md p-8">
|
|
39
|
-
<
|
|
40
|
-
{t("recovery.forgotTitle")}
|
|
41
|
-
</h1>
|
|
40
|
+
<BrandHeader title={t("recovery.forgotTitle")} />
|
|
42
41
|
{sent ? (
|
|
43
42
|
<Alert variant="success">{t("recovery.forgotSuccess")}</Alert>
|
|
44
43
|
) : (
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
PasswordTokenStatus,
|
|
14
14
|
} from "#core/_services/auth";
|
|
15
15
|
import { STRONG_PASSWORD_REGEX } from "#core/_utils/password";
|
|
16
|
-
import { PasswordField } from "#core/components";
|
|
16
|
+
import { BrandHeader, PasswordField } from "#core/components";
|
|
17
17
|
import { Alert, Button, Card, Field, Spinner } from "#core/components/ui";
|
|
18
18
|
import { useI18n } from "#core/contexts";
|
|
19
19
|
import { useRequest } from "#core/hooks/use-request";
|
|
@@ -92,7 +92,7 @@ export function ResetPasswordScreen(): JSX.Element {
|
|
|
92
92
|
return (
|
|
93
93
|
<div className="flex min-h-screen items-center justify-center bg-background p-4">
|
|
94
94
|
<Card className="w-full max-w-md p-8">
|
|
95
|
-
<
|
|
95
|
+
<BrandHeader title={title} />
|
|
96
96
|
|
|
97
97
|
{checking && (
|
|
98
98
|
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|