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.
- package/README.md +129 -0
- package/package.json +113 -0
- package/src/_services/api/axios.factory.ts +97 -0
- package/src/_services/api/list-query.ts +26 -0
- package/src/_services/api/schema.d.ts +2527 -0
- package/src/_services/auth/index.ts +127 -0
- package/src/_utils/advanced-filter.ts +386 -0
- package/src/_utils/calendar.ts +103 -0
- package/src/_utils/crop-image.ts +65 -0
- package/src/_utils/filter.ts +313 -0
- package/src/_utils/format.ts +35 -0
- package/src/_utils/initials.ts +24 -0
- package/src/_utils/password.ts +7 -0
- package/src/components/app-shell.tsx +314 -0
- package/src/components/breadcrumbs.tsx +55 -0
- package/src/components/index.ts +7 -0
- package/src/components/language-selector.tsx +51 -0
- package/src/components/password-field.tsx +40 -0
- package/src/components/theme-toggle.tsx +27 -0
- package/src/components/ui/alert.tsx +27 -0
- package/src/components/ui/avatar.tsx +50 -0
- package/src/components/ui/badge.tsx +36 -0
- package/src/components/ui/button.tsx +62 -0
- package/src/components/ui/card.tsx +86 -0
- package/src/components/ui/checkbox.tsx +30 -0
- package/src/components/ui/column-visibility-modal.tsx +110 -0
- package/src/components/ui/confirm-dialog.tsx +69 -0
- package/src/components/ui/data-table.tsx +401 -0
- package/src/components/ui/date-range-picker.tsx +250 -0
- package/src/components/ui/dialog.tsx +138 -0
- package/src/components/ui/dropdown-menu.tsx +108 -0
- package/src/components/ui/field.tsx +20 -0
- package/src/components/ui/filter-chips.tsx +88 -0
- package/src/components/ui/filter-field.tsx +137 -0
- package/src/components/ui/filter-group.tsx +155 -0
- package/src/components/ui/filter-rule.tsx +221 -0
- package/src/components/ui/filter-sheet.tsx +201 -0
- package/src/components/ui/index.ts +30 -0
- package/src/components/ui/input.tsx +25 -0
- package/src/components/ui/label.tsx +23 -0
- package/src/components/ui/row-actions.tsx +32 -0
- package/src/components/ui/select.tsx +97 -0
- package/src/components/ui/separator.tsx +31 -0
- package/src/components/ui/sheet.tsx +119 -0
- package/src/components/ui/sonner.tsx +28 -0
- package/src/components/ui/spinner.tsx +12 -0
- package/src/components/ui/switch.tsx +29 -0
- package/src/components/ui/table.tsx +84 -0
- package/src/components/ui/tabs.tsx +52 -0
- package/src/components/user-avatar.tsx +54 -0
- package/src/contexts/auth-context.tsx +90 -0
- package/src/contexts/color-mode-context.tsx +105 -0
- package/src/contexts/i18n-context.tsx +76 -0
- package/src/contexts/index.ts +6 -0
- package/src/contexts/socket-context.tsx +153 -0
- package/src/contexts/toast-context.tsx +40 -0
- package/src/features/audit/audit-screen.tsx +299 -0
- package/src/features/audit/hooks/use-audit-trail.ts +68 -0
- package/src/features/audit/services/audit.service.ts +24 -0
- package/src/features/dashboard/dashboard-screen.tsx +73 -0
- package/src/features/login/components/backup-codes-dialog.tsx +66 -0
- package/src/features/login/components/index.ts +5 -0
- package/src/features/login/components/login-form.tsx +62 -0
- package/src/features/login/components/two-factor-setup.tsx +71 -0
- package/src/features/login/components/two-factor-verify.tsx +56 -0
- package/src/features/login/hooks/use-login-flow.ts +119 -0
- package/src/features/login/login-screen.tsx +83 -0
- package/src/features/login/validation/schemas.ts +30 -0
- package/src/features/logs/hooks/use-error-logs.ts +65 -0
- package/src/features/logs/hooks/use-request-logs.ts +65 -0
- package/src/features/logs/logs-screen.tsx +31 -0
- package/src/features/logs/panels/error-logs-panel.tsx +183 -0
- package/src/features/logs/panels/index.ts +3 -0
- package/src/features/logs/panels/request-logs-panel.tsx +124 -0
- package/src/features/logs/services/logs.service.ts +68 -0
- package/src/features/notifications/components/notification-item.tsx +67 -0
- package/src/features/notifications/hooks/use-notifications.ts +78 -0
- package/src/features/notifications/notifications-center.tsx +119 -0
- package/src/features/notifications/services/notifications.service.ts +38 -0
- package/src/features/profile/components/avatar-crop-dialog.tsx +133 -0
- package/src/features/profile/components/index.ts +2 -0
- package/src/features/profile/force-password-change.tsx +114 -0
- package/src/features/profile/profile-screen.tsx +385 -0
- package/src/features/rbac/panels/actions-panel.tsx +20 -0
- package/src/features/rbac/panels/catalog-panel.tsx +243 -0
- package/src/features/rbac/panels/groups-panel.tsx +269 -0
- package/src/features/rbac/panels/index.ts +8 -0
- package/src/features/rbac/panels/permissions-panel.tsx +204 -0
- package/src/features/rbac/panels/resources-panel.tsx +20 -0
- package/src/features/rbac/panels/roles-panel.tsx +224 -0
- package/src/features/rbac/panels/scopes-panel.tsx +20 -0
- package/src/features/rbac/rbac-screen.tsx +80 -0
- package/src/features/rbac/services/rbac.service.ts +110 -0
- package/src/features/recovery/forgot-password-screen.tsx +69 -0
- package/src/features/recovery/reset-password-screen.tsx +163 -0
- package/src/features/users/components/user-form-dialog.tsx +187 -0
- package/src/features/users/hooks/use-users.ts +181 -0
- package/src/features/users/services/users.service.ts +83 -0
- package/src/features/users/users-screen.tsx +331 -0
- package/src/hooks/use-column-visibility.ts +54 -0
- package/src/hooks/use-confirm.tsx +70 -0
- package/src/hooks/use-filter-schema.ts +85 -0
- package/src/hooks/use-filters.ts +148 -0
- package/src/hooks/use-list-query.ts +69 -0
- package/src/hooks/use-request.ts +114 -0
- package/src/i18n/messages/en.ts +296 -0
- package/src/i18n/messages/pt.ts +305 -0
- package/src/index.ts +60 -0
- package/src/lib/utils.ts +6 -0
- package/src/middleware.ts +42 -0
- package/src/styles/core.css +142 -0
- package/tailwind-preset.ts +103 -0
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
export const pt = {
|
|
2
|
+
app: { name: "Core App" },
|
|
3
|
+
nav: {
|
|
4
|
+
home: "Início",
|
|
5
|
+
admin: "Administração",
|
|
6
|
+
dashboard: "Dashboard",
|
|
7
|
+
users: "Usuários",
|
|
8
|
+
rbac: "Papéis & Permissões",
|
|
9
|
+
profile: "Meu perfil",
|
|
10
|
+
logs: "Logs",
|
|
11
|
+
audit: "Auditoria",
|
|
12
|
+
},
|
|
13
|
+
logs: {
|
|
14
|
+
title: "Logs",
|
|
15
|
+
subtitle: "Toda requisição à API — sucesso e erro, rota, status e duração.",
|
|
16
|
+
tabRequests: "Requisições",
|
|
17
|
+
tabErrors: "Erros",
|
|
18
|
+
date: "Data/hora",
|
|
19
|
+
route: "Rota",
|
|
20
|
+
status: "Status",
|
|
21
|
+
duration: "Duração",
|
|
22
|
+
user: "Usuário",
|
|
23
|
+
systemUser: "Sistema",
|
|
24
|
+
ip: "IP",
|
|
25
|
+
requestId: "Request ID",
|
|
26
|
+
type: "Tipo",
|
|
27
|
+
message: "Mensagem",
|
|
28
|
+
viewDetails: "Ver detalhes",
|
|
29
|
+
detailsTitle: "Detalhes do erro",
|
|
30
|
+
stackTrace: "Stack trace",
|
|
31
|
+
},
|
|
32
|
+
auditTrail: {
|
|
33
|
+
title: "Trilha de Auditoria",
|
|
34
|
+
subtitle: "Histórico de alterações de dados — quem mudou o quê, e quando.",
|
|
35
|
+
entity: "Entidade",
|
|
36
|
+
action: "Ação",
|
|
37
|
+
actionCreate: "Criação",
|
|
38
|
+
actionUpdate: "Alteração",
|
|
39
|
+
actionDelete: "Remoção",
|
|
40
|
+
user: "Usuário",
|
|
41
|
+
systemUser: "Sistema",
|
|
42
|
+
date: "Data/hora",
|
|
43
|
+
ip: "IP",
|
|
44
|
+
viewChanges: "Ver alterações",
|
|
45
|
+
changesTitle: "Alterações",
|
|
46
|
+
field: "Campo",
|
|
47
|
+
before: "Antes",
|
|
48
|
+
after: "Depois",
|
|
49
|
+
noFields: "Nenhum campo registrado nesta alteração.",
|
|
50
|
+
showTechnical: "Mostrar campos técnicos",
|
|
51
|
+
},
|
|
52
|
+
/**
|
|
53
|
+
* Rótulo das colunas que aparecem no diff da auditoria. Campo sem chave aqui
|
|
54
|
+
* é exibido com o nome cru — não quebra quando nasce coluna nova no backend.
|
|
55
|
+
*/
|
|
56
|
+
auditFields: {
|
|
57
|
+
name: "Nome",
|
|
58
|
+
lastName: "Sobrenome",
|
|
59
|
+
email: "Email",
|
|
60
|
+
phone: "Telefone",
|
|
61
|
+
isActive: "Ativo",
|
|
62
|
+
avatar: "Avatar",
|
|
63
|
+
twoFactorEnabled: "2FA ativo",
|
|
64
|
+
failedLoginAttempts: "Tentativas de login falhas",
|
|
65
|
+
mustChangePassword: "Precisa trocar a senha",
|
|
66
|
+
passwordChangedAt: "Senha alterada em",
|
|
67
|
+
createdAt: "Criado em",
|
|
68
|
+
updatedAt: "Atualizado em",
|
|
69
|
+
deletedAt: "Removido em",
|
|
70
|
+
description: "Descrição",
|
|
71
|
+
code: "Código",
|
|
72
|
+
permissions: "Permissões",
|
|
73
|
+
roles: "Papéis",
|
|
74
|
+
},
|
|
75
|
+
table: {
|
|
76
|
+
columns: "Colunas",
|
|
77
|
+
columnsButton: "Colunas",
|
|
78
|
+
columnsTitle: "Configurar visualização de colunas",
|
|
79
|
+
columnsHint: "Selecione as colunas que deseja visualizar na tabela.",
|
|
80
|
+
atLeastOne: "Pelo menos uma coluna deve permanecer visível",
|
|
81
|
+
results: "resultados",
|
|
82
|
+
perPage: "por página",
|
|
83
|
+
empty: "Nenhum registro",
|
|
84
|
+
},
|
|
85
|
+
filters: {
|
|
86
|
+
button: "Filtros",
|
|
87
|
+
title: "Filtros",
|
|
88
|
+
hint: "Preencha só os campos que quiser usar. Tudo é combinado com E.",
|
|
89
|
+
apply: "Aplicar",
|
|
90
|
+
clear: "Limpar",
|
|
91
|
+
clearAll: "Limpar tudo",
|
|
92
|
+
empty: "Esta listagem ainda não tem filtros.",
|
|
93
|
+
noOptions: "Nenhuma opção disponível",
|
|
94
|
+
all: "Todos",
|
|
95
|
+
from: "De",
|
|
96
|
+
to: "Até",
|
|
97
|
+
fromChip: "a partir de",
|
|
98
|
+
toChip: "até",
|
|
99
|
+
selectPeriod: "Selecione o período",
|
|
100
|
+
lastDays: "{days} dias",
|
|
101
|
+
previousMonth: "Mês anterior",
|
|
102
|
+
nextMonth: "Próximo mês",
|
|
103
|
+
simple: "Simples",
|
|
104
|
+
advanced: "Avançado",
|
|
105
|
+
advancedHint:
|
|
106
|
+
"Monte a expressão: cada grupo combina suas condições com E ou OU.",
|
|
107
|
+
addCondition: "Condição",
|
|
108
|
+
addGroup: "Grupo",
|
|
109
|
+
and: "E",
|
|
110
|
+
or: "OU",
|
|
111
|
+
emptyGroup: "Grupo sem condições.",
|
|
112
|
+
removeRule: "Remover condição",
|
|
113
|
+
removeGroup: "Remover grupo",
|
|
114
|
+
advancedSummary: "Filtro avançado · {count} condições",
|
|
115
|
+
field: "Campo",
|
|
116
|
+
operator: "Operador",
|
|
117
|
+
value: "Valor",
|
|
118
|
+
null: "Vazio (nulo)",
|
|
119
|
+
listHint: "Separe por vírgula",
|
|
120
|
+
tooDeep: "Grupos aninhados demais — o limite é 3 níveis.",
|
|
121
|
+
tooManyConditions: "Condições demais — o limite é 50.",
|
|
122
|
+
tooManyItems: "Itens demais numa lista — o limite é 100.",
|
|
123
|
+
operators: {
|
|
124
|
+
EQ: "igual a",
|
|
125
|
+
NE: "diferente de",
|
|
126
|
+
IS: "é",
|
|
127
|
+
NOT: "não é",
|
|
128
|
+
GT: "maior que",
|
|
129
|
+
GTE: "maior ou igual a",
|
|
130
|
+
LT: "menor que",
|
|
131
|
+
LTE: "menor ou igual a",
|
|
132
|
+
BETWEEN: "entre",
|
|
133
|
+
NOTBETWEEN: "fora de",
|
|
134
|
+
IN: "em",
|
|
135
|
+
NOTIN: "fora da lista",
|
|
136
|
+
LIKE: "corresponde a",
|
|
137
|
+
NOTLIKE: "não corresponde a",
|
|
138
|
+
STARTSWITH: "começa com",
|
|
139
|
+
ENDSWITH: "termina com",
|
|
140
|
+
SUBSTRING: "contém",
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
notifications: {
|
|
144
|
+
title: "Notificações",
|
|
145
|
+
all: "Todas",
|
|
146
|
+
unread: "Não lidas",
|
|
147
|
+
markAllRead: "Marcar todas como lidas",
|
|
148
|
+
empty: "Nenhuma notificação",
|
|
149
|
+
},
|
|
150
|
+
common: {
|
|
151
|
+
save: "Salvar",
|
|
152
|
+
cancel: "Cancelar",
|
|
153
|
+
edit: "Editar",
|
|
154
|
+
delete: "Excluir",
|
|
155
|
+
create: "Criar",
|
|
156
|
+
confirm: "Confirmar",
|
|
157
|
+
send: "Enviar",
|
|
158
|
+
yes: "Sim",
|
|
159
|
+
no: "Não",
|
|
160
|
+
actions: "Ações",
|
|
161
|
+
loading: "Carregando...",
|
|
162
|
+
language: "Idioma",
|
|
163
|
+
theme: "Tema",
|
|
164
|
+
light: "Claro",
|
|
165
|
+
dark: "Escuro",
|
|
166
|
+
system: "Sistema",
|
|
167
|
+
// Mensagens de sucesso genéricas — só para tela reaproveitada, que não sabe
|
|
168
|
+
// qual entidade está editando. Tela de entidade fixa usa texto próprio.
|
|
169
|
+
createdSuccess: "Registro criado com sucesso",
|
|
170
|
+
updatedSuccess: "Registro atualizado com sucesso",
|
|
171
|
+
deletedSuccess: "Registro excluído com sucesso",
|
|
172
|
+
},
|
|
173
|
+
auth: {
|
|
174
|
+
loginTitle: "Entrar",
|
|
175
|
+
email: "Email",
|
|
176
|
+
password: "Senha",
|
|
177
|
+
enter: "Entrar",
|
|
178
|
+
entering: "Entrando...",
|
|
179
|
+
forgotPassword: "Esqueci minha senha",
|
|
180
|
+
twoFactorSetupTitle: "Configurar 2FA",
|
|
181
|
+
twoFactorVerifyTitle: "Verificação em duas etapas",
|
|
182
|
+
backupTitle: "Códigos de backup",
|
|
183
|
+
setupHint:
|
|
184
|
+
"Configure a autenticação em duas etapas (obrigatória). Escaneie o QR com um app autenticador gratuito (Google Authenticator, Microsoft Authenticator, Authy...) e informe o código.",
|
|
185
|
+
manualKey: "Não consegue escanear? Use a chave manual:",
|
|
186
|
+
codeApp: "Código do app (6 dígitos)",
|
|
187
|
+
confirmActivate: "Confirmar e ativar 2FA",
|
|
188
|
+
confirming: "Confirmando...",
|
|
189
|
+
verifyHint:
|
|
190
|
+
"Informe o código do seu app autenticador. Sem acesso ao app? Use um código de backup ou receba um código por email.",
|
|
191
|
+
codeAny: "Código (TOTP, email ou backup)",
|
|
192
|
+
verify: "Validar código",
|
|
193
|
+
verifying: "Validando...",
|
|
194
|
+
receiveByEmail: "Receber código por email",
|
|
195
|
+
sendingEmail: "Enviando...",
|
|
196
|
+
backupWarning:
|
|
197
|
+
"Estes códigos só aparecem agora. Cada um pode ser usado uma vez caso você perca o acesso ao app.",
|
|
198
|
+
copy: "Copiar",
|
|
199
|
+
codesCopied: "Códigos de backup copiados",
|
|
200
|
+
savedContinue: "Guardei, continuar",
|
|
201
|
+
loginSuccess: "Login realizado com sucesso",
|
|
202
|
+
twoFactorConfigured: "2FA configurado com sucesso",
|
|
203
|
+
codeSent: "Código enviado para seu email",
|
|
204
|
+
},
|
|
205
|
+
recovery: {
|
|
206
|
+
forgotTitle: "Recuperar senha",
|
|
207
|
+
forgotHint:
|
|
208
|
+
"Informe seu email e enviaremos um link para redefinir a senha.",
|
|
209
|
+
sendLink: "Enviar link",
|
|
210
|
+
sending: "Enviando...",
|
|
211
|
+
forgotSuccess: "Se o email existir, enviamos as instruções de redefinição.",
|
|
212
|
+
backToLogin: "Voltar ao login",
|
|
213
|
+
resetTitle: "Redefinir senha",
|
|
214
|
+
newPassword: "Nova senha",
|
|
215
|
+
confirmPassword: "Confirmar nova senha",
|
|
216
|
+
resetCta: "Redefinir senha",
|
|
217
|
+
resetSuccess: "Senha redefinida com sucesso",
|
|
218
|
+
checking: "Verificando o link...",
|
|
219
|
+
firstAccessTitle: "Definir senha de primeiro acesso",
|
|
220
|
+
firstAccessHint:
|
|
221
|
+
"Crie sua senha para acessar o sistema. No primeiro login você também configurará o 2FA.",
|
|
222
|
+
linkExpired: "Este link expirou. Solicite um novo link para definir sua senha.",
|
|
223
|
+
linkUsed: "Este link já foi utilizado. Solicite um novo link.",
|
|
224
|
+
linkInvalid: "Link inválido. Solicite um novo link para definir sua senha.",
|
|
225
|
+
requestNewLink: "Solicitar novo link",
|
|
226
|
+
},
|
|
227
|
+
user: { menuProfile: "Meu perfil", logout: "Sair" },
|
|
228
|
+
dashboard: {
|
|
229
|
+
welcome: "Bem-vindo(a), {name}",
|
|
230
|
+
roles: "Papéis",
|
|
231
|
+
permissions: "Permissões",
|
|
232
|
+
security: "Segurança",
|
|
233
|
+
twoFactorActive: "2FA ativo",
|
|
234
|
+
twoFactorPending: "2FA pendente",
|
|
235
|
+
},
|
|
236
|
+
users: {
|
|
237
|
+
title: "Usuários",
|
|
238
|
+
newUser: "Novo usuário",
|
|
239
|
+
name: "Nome",
|
|
240
|
+
lastName: "Sobrenome",
|
|
241
|
+
phone: "Telefone",
|
|
242
|
+
email: "Email",
|
|
243
|
+
roles: "Papéis",
|
|
244
|
+
status: "Status",
|
|
245
|
+
active: "Ativo",
|
|
246
|
+
inactive: "Inativo",
|
|
247
|
+
locked: "Bloqueado",
|
|
248
|
+
passwordByEmailHint:
|
|
249
|
+
"O usuário receberá um link por email para definir a própria senha. Nenhuma senha é definida aqui.",
|
|
250
|
+
sendFirstAccessLink: "Enviar link de primeiro acesso",
|
|
251
|
+
sendPasswordResetLink: "Enviar link de redefinição de senha",
|
|
252
|
+
passwordResetLinkSent:
|
|
253
|
+
"Link de redefinição enviado — as sessões ativas do usuário foram encerradas",
|
|
254
|
+
firstAccessLinkSent:
|
|
255
|
+
"Link de primeiro acesso enviado para o email do usuário",
|
|
256
|
+
resetTwoFactor: "Resetar 2FA",
|
|
257
|
+
twoFactorReset: "2FA resetado — o usuário reconfigura no próximo login",
|
|
258
|
+
},
|
|
259
|
+
profile: {
|
|
260
|
+
title: "Meu perfil",
|
|
261
|
+
data: "Dados",
|
|
262
|
+
changePassword: "Alterar senha",
|
|
263
|
+
currentPassword: "Senha atual",
|
|
264
|
+
newPassword: "Nova senha",
|
|
265
|
+
confirmPassword: "Confirmar nova senha",
|
|
266
|
+
changed: "Senha alterada com sucesso",
|
|
267
|
+
forceProvisional:
|
|
268
|
+
"Você está usando uma senha provisória. Defina uma nova senha para continuar.",
|
|
269
|
+
forceExpired: "Sua senha expirou. Defina uma nova senha para continuar.",
|
|
270
|
+
changeAvatar: "Alterar foto",
|
|
271
|
+
cropTitle: "Recortar foto",
|
|
272
|
+
cropHint: "Arraste para posicionar e use o zoom para ajustar.",
|
|
273
|
+
cropZoom: "Zoom",
|
|
274
|
+
saved: "Perfil atualizado",
|
|
275
|
+
avatarUpdated: "Foto de perfil atualizada",
|
|
276
|
+
avatarRemoved: "Foto de perfil removida",
|
|
277
|
+
},
|
|
278
|
+
rbac: { title: "Papéis & Permissões" },
|
|
279
|
+
validation: {
|
|
280
|
+
required: "Campo obrigatório",
|
|
281
|
+
email: "Email inválido",
|
|
282
|
+
passwordStrong:
|
|
283
|
+
"Mín. 8 caracteres com maiúscula, minúscula, número e símbolo",
|
|
284
|
+
passwordsNotMatch: "As senhas não conferem",
|
|
285
|
+
codeShort: "Código muito curto",
|
|
286
|
+
},
|
|
287
|
+
errors: {
|
|
288
|
+
unexpected: "Erro inesperado",
|
|
289
|
+
// Texto por `errorCode` do backend. Só entra aqui o código cuja mensagem é
|
|
290
|
+
// fixa — quando o servidor manda dado dinâmico (tentativas restantes, por
|
|
291
|
+
// exemplo), a mensagem dele é melhor e o `use-request` a mantém.
|
|
292
|
+
codes: {
|
|
293
|
+
ACCOUNT_INACTIVE:
|
|
294
|
+
"Conta desativada. Entre em contato com o administrador do sistema.",
|
|
295
|
+
ACCOUNT_LOCKED:
|
|
296
|
+
"Conta bloqueada por excesso de tentativas. Peça ao administrador o envio de um novo link para definir sua senha.",
|
|
297
|
+
PASSWORD_REUSED:
|
|
298
|
+
"Esta senha já foi utilizada. Escolha uma senha que você ainda não usou neste sistema.",
|
|
299
|
+
TOO_MANY_REQUESTS:
|
|
300
|
+
"Muitas tentativas em pouco tempo. Aguarde um instante e tente de novo.",
|
|
301
|
+
},
|
|
302
|
+
},
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
export type Messages = typeof pt;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Superfície pública do `rl-core-front`.
|
|
3
|
+
*
|
|
4
|
+
* É o contrato: o que sai daqui é o que um projeto pode importar, e mudar ou
|
|
5
|
+
* remover algo desta lista é *breaking change* — vale um major no semver.
|
|
6
|
+
*
|
|
7
|
+
* O CSS não passa por aqui, porque CSS não se importa de arquivo TypeScript:
|
|
8
|
+
* `@import "rl-core-front/styles.css"` no globals.css do projeto.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// Telas — cada page.tsx do projeto reexporta a que quiser
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
export { AuditScreen } from "#core/features/audit/audit-screen";
|
|
15
|
+
export { DashboardScreen } from "#core/features/dashboard/dashboard-screen";
|
|
16
|
+
export { LoginScreen } from "#core/features/login/login-screen";
|
|
17
|
+
export { LogsScreen } from "#core/features/logs/logs-screen";
|
|
18
|
+
export { ProfileScreen } from "#core/features/profile/profile-screen";
|
|
19
|
+
export { RbacScreen } from "#core/features/rbac/rbac-screen";
|
|
20
|
+
export { ForgotPasswordScreen } from "#core/features/recovery/forgot-password-screen";
|
|
21
|
+
export { ResetPasswordScreen } from "#core/features/recovery/reset-password-screen";
|
|
22
|
+
export { UsersScreen } from "#core/features/users/users-screen";
|
|
23
|
+
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// Providers e shell — vão no layout.tsx do projeto
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
export * from "#core/components";
|
|
28
|
+
export * from "#core/contexts";
|
|
29
|
+
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
// Primitivos de UI — agnósticos de domínio
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
export * from "#core/components/ui";
|
|
34
|
+
export { cn } from "#core/lib/utils";
|
|
35
|
+
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// Hooks de listagem, requisição e filtro
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
export type { UseColumnVisibilityResult } from "#core/hooks/use-column-visibility";
|
|
40
|
+
export { useColumnVisibility } from "#core/hooks/use-column-visibility";
|
|
41
|
+
export type { ConfirmOptions, UseConfirmResult } from "#core/hooks/use-confirm";
|
|
42
|
+
export { useConfirm } from "#core/hooks/use-confirm";
|
|
43
|
+
export type { UseFilterSchemaResult } from "#core/hooks/use-filter-schema";
|
|
44
|
+
export { useFilterSchema } from "#core/hooks/use-filter-schema";
|
|
45
|
+
export type { FilterMode, FiltersState } from "#core/hooks/use-filters";
|
|
46
|
+
export { useFilters } from "#core/hooks/use-filters";
|
|
47
|
+
export type { UseListQueryResult } from "#core/hooks/use-list-query";
|
|
48
|
+
export { useListQuery } from "#core/hooks/use-list-query";
|
|
49
|
+
export type { RunOptions, UseRequestResult } from "#core/hooks/use-request";
|
|
50
|
+
export { useRequest } from "#core/hooks/use-request";
|
|
51
|
+
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// HTTP e traduções
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
export { api } from "#core/_services/api/axios.factory";
|
|
56
|
+
export type { ListQuery } from "#core/_services/api/list-query";
|
|
57
|
+
export { toListParams } from "#core/_services/api/list-query";
|
|
58
|
+
export { en } from "#core/i18n/messages/en";
|
|
59
|
+
export type { Messages } from "#core/i18n/messages/pt";
|
|
60
|
+
export { pt } from "#core/i18n/messages/pt";
|
package/src/lib/utils.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Protege as rotas privadas verificando a presença do cookie de sessão.
|
|
5
|
+
* - Sem access_token → manda para /login.
|
|
6
|
+
* - Com access_token tentando acessar /login → manda para /dashboard.
|
|
7
|
+
*
|
|
8
|
+
* Observação: a validação forte do token é feita no backend; aqui é só
|
|
9
|
+
* o "portão" de roteamento (UX), evitando flicker de telas privadas.
|
|
10
|
+
*/
|
|
11
|
+
const PUBLIC_PATHS = ["/login", "/forgot-password", "/reset-password"];
|
|
12
|
+
|
|
13
|
+
export function middleware(req: NextRequest): NextResponse<unknown> {
|
|
14
|
+
const { pathname } = req.nextUrl;
|
|
15
|
+
const hasSession =
|
|
16
|
+
req.cookies.has("access_token") || req.cookies.has("refresh_token");
|
|
17
|
+
|
|
18
|
+
const isPublic = PUBLIC_PATHS.some((p) => pathname.startsWith(p));
|
|
19
|
+
|
|
20
|
+
if (!hasSession && !isPublic) {
|
|
21
|
+
const url = req.nextUrl.clone();
|
|
22
|
+
url.pathname = "/login";
|
|
23
|
+
return NextResponse.redirect(url);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// reason=expired: o front detectou (via backend) que a sessão é inválida e está
|
|
27
|
+
// limpando os cookies — não manda de volta pro dashboard nesse meio-tempo, senão
|
|
28
|
+
// vira loop entre /dashboard e /login enquanto os cookies antigos ainda existem.
|
|
29
|
+
const isExpiredBounce = req.nextUrl.searchParams.get("reason") === "expired";
|
|
30
|
+
|
|
31
|
+
if (hasSession && pathname === "/login" && !isExpiredBounce) {
|
|
32
|
+
const url = req.nextUrl.clone();
|
|
33
|
+
url.pathname = "/dashboard";
|
|
34
|
+
return NextResponse.redirect(url);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return NextResponse.next();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const config = {
|
|
41
|
+
matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
|
|
42
|
+
};
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Design system do rl-core-front: tokens de cor, base do body, correção do
|
|
3
|
+
* autofill e a barra de progresso do toast.
|
|
4
|
+
*
|
|
5
|
+
* Um projeto que consome o pacote importa este arquivo no globals.css dele,
|
|
6
|
+
* depois do `@import "tailwindcss"`:
|
|
7
|
+
*
|
|
8
|
+
* @import "tailwindcss";
|
|
9
|
+
* @config "../../tailwind.config.ts";
|
|
10
|
+
* @import "rl-core-front/styles.css";
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@layer base {
|
|
15
|
+
:root {
|
|
16
|
+
/*
|
|
17
|
+
* O Chrome resolve a cor do autofill (e da scrollbar, e dos controles
|
|
18
|
+
* nativos) por `color-scheme`, não pela nossa classe `.dark`. Sem declarar,
|
|
19
|
+
* ele assume claro e pinta o campo preenchido de azul-claro mesmo no tema
|
|
20
|
+
* escuro. É a causa raiz do input branco.
|
|
21
|
+
*/
|
|
22
|
+
color-scheme: light;
|
|
23
|
+
--background: 210 20% 98%;
|
|
24
|
+
--foreground: 222 47% 11%;
|
|
25
|
+
--card: 0 0% 100%;
|
|
26
|
+
--card-foreground: 222 47% 11%;
|
|
27
|
+
--popover: 0 0% 100%;
|
|
28
|
+
--popover-foreground: 222 47% 11%;
|
|
29
|
+
--primary: 211 80% 42%;
|
|
30
|
+
--primary-foreground: 0 0% 100%;
|
|
31
|
+
--secondary: 210 40% 96%;
|
|
32
|
+
--secondary-foreground: 222 47% 11%;
|
|
33
|
+
--muted: 210 40% 96%;
|
|
34
|
+
--muted-foreground: 215 16% 47%;
|
|
35
|
+
--accent: 210 40% 96%;
|
|
36
|
+
--accent-foreground: 222 47% 11%;
|
|
37
|
+
--destructive: 0 72% 51%;
|
|
38
|
+
--destructive-foreground: 0 0% 100%;
|
|
39
|
+
--success: 142 71% 45%;
|
|
40
|
+
--success-foreground: 0 0% 100%;
|
|
41
|
+
--warning: 38 92% 50%;
|
|
42
|
+
--warning-foreground: 0 0% 100%;
|
|
43
|
+
--border: 214 32% 91%;
|
|
44
|
+
--input: 214 32% 91%;
|
|
45
|
+
--ring: 211 80% 42%;
|
|
46
|
+
--radius: 0.6rem;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.dark {
|
|
50
|
+
color-scheme: dark;
|
|
51
|
+
--background: 210 30% 8%;
|
|
52
|
+
--foreground: 210 20% 92%;
|
|
53
|
+
--card: 213 25% 13%;
|
|
54
|
+
--card-foreground: 210 20% 92%;
|
|
55
|
+
--popover: 213 25% 13%;
|
|
56
|
+
--popover-foreground: 210 20% 92%;
|
|
57
|
+
--primary: 211 85% 60%;
|
|
58
|
+
--primary-foreground: 213 30% 10%;
|
|
59
|
+
--secondary: 213 20% 18%;
|
|
60
|
+
--secondary-foreground: 210 20% 92%;
|
|
61
|
+
--muted: 213 20% 18%;
|
|
62
|
+
--muted-foreground: 215 15% 65%;
|
|
63
|
+
--accent: 213 20% 22%;
|
|
64
|
+
--accent-foreground: 210 20% 92%;
|
|
65
|
+
--destructive: 0 63% 50%;
|
|
66
|
+
--destructive-foreground: 0 0% 100%;
|
|
67
|
+
--success: 142 65% 45%;
|
|
68
|
+
--success-foreground: 0 0% 100%;
|
|
69
|
+
--warning: 38 92% 55%;
|
|
70
|
+
--warning-foreground: 213 30% 10%;
|
|
71
|
+
--border: 213 20% 22%;
|
|
72
|
+
--input: 213 20% 24%;
|
|
73
|
+
--ring: 211 85% 60%;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
@layer base {
|
|
78
|
+
* {
|
|
79
|
+
@apply border-border;
|
|
80
|
+
}
|
|
81
|
+
body {
|
|
82
|
+
@apply bg-background text-foreground;
|
|
83
|
+
font-feature-settings: 'rlig' 1, 'calt' 1;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/*
|
|
87
|
+
* Preenchimento automático (gerenciador de senhas). O `background-color` do
|
|
88
|
+
* campo preenchido é intocável por CSS de autor — o Chrome o declara com
|
|
89
|
+
* `!important` na folha do próprio browser, e regra de UA com `!important`
|
|
90
|
+
* ganha até de `!important` de autor. A saída é cobrir com sombra interna.
|
|
91
|
+
*
|
|
92
|
+
* A sombra vai na variável `--tw-inset-shadow` em vez de em `box-shadow`
|
|
93
|
+
* direto: o Tailwind v4 compõe o `box-shadow` do elemento a partir dela e de
|
|
94
|
+
* `--tw-ring-shadow`. Sobrescrever `box-shadow` (o que exigiria `!important`,
|
|
95
|
+
* já que `shadow-sm` mora na camada `utilities`) apagaria o anel de foco do
|
|
96
|
+
* input. Alimentando a variável, fundo e anel convivem.
|
|
97
|
+
*
|
|
98
|
+
* O `box-shadow` solto abaixo cobre o input que não usa utilitário de sombra,
|
|
99
|
+
* onde não existe composição para a variável entrar.
|
|
100
|
+
*/
|
|
101
|
+
input:-webkit-autofill,
|
|
102
|
+
input:-webkit-autofill:hover,
|
|
103
|
+
input:-webkit-autofill:focus,
|
|
104
|
+
input:-webkit-autofill:active,
|
|
105
|
+
textarea:-webkit-autofill,
|
|
106
|
+
select:-webkit-autofill {
|
|
107
|
+
--tw-inset-shadow: 0 0 0 1000px hsl(var(--background)) inset;
|
|
108
|
+
box-shadow: 0 0 0 1000px hsl(var(--background)) inset;
|
|
109
|
+
-webkit-text-fill-color: hsl(var(--foreground));
|
|
110
|
+
caret-color: hsl(var(--foreground));
|
|
111
|
+
/* Atrasa a transição do fundo para nunca ver o amarelo inicial do Chrome. */
|
|
112
|
+
transition: background-color 9999s ease-out 0s;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/* Barra de progresso do toast — some ao passar o mouse (sonner já pausa o dismiss no hover) */
|
|
117
|
+
[data-sonner-toast] {
|
|
118
|
+
position: relative;
|
|
119
|
+
overflow: hidden;
|
|
120
|
+
}
|
|
121
|
+
[data-sonner-toast]::after {
|
|
122
|
+
content: '';
|
|
123
|
+
position: absolute;
|
|
124
|
+
left: 0;
|
|
125
|
+
bottom: 0;
|
|
126
|
+
height: 3px;
|
|
127
|
+
width: 100%;
|
|
128
|
+
background: currentColor;
|
|
129
|
+
opacity: 0.35;
|
|
130
|
+
animation: toast-progress 4s linear forwards;
|
|
131
|
+
}
|
|
132
|
+
[data-sonner-toast]:hover::after {
|
|
133
|
+
animation-play-state: paused;
|
|
134
|
+
}
|
|
135
|
+
@keyframes toast-progress {
|
|
136
|
+
from {
|
|
137
|
+
width: 100%;
|
|
138
|
+
}
|
|
139
|
+
to {
|
|
140
|
+
width: 0%;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import type { Config } from 'tailwindcss';
|
|
2
|
+
import tailwindcssAnimate from 'tailwindcss-animate';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Preset do rl-core-front: tema, paleta e plugins.
|
|
6
|
+
*
|
|
7
|
+
* As cores apontam para variáveis CSS (`--primary`, `--background`…), cujos
|
|
8
|
+
* valores estão em `styles/core.css`. Os dois andam juntos — usar o preset sem
|
|
9
|
+
* importar o CSS deixa toda cor sem valor.
|
|
10
|
+
*
|
|
11
|
+
* Não traz `content`: o que varrer é decisão de cada projeto, que precisa
|
|
12
|
+
* incluir o próprio `src` **e** o do pacote dentro de node_modules.
|
|
13
|
+
*
|
|
14
|
+
* ```ts
|
|
15
|
+
* // tailwind.config.ts do projeto
|
|
16
|
+
* import corePreset from "rl-core-front/tailwind-preset";
|
|
17
|
+
*
|
|
18
|
+
* export default {
|
|
19
|
+
* presets: [corePreset],
|
|
20
|
+
* content: [
|
|
21
|
+
* "./src/**\/*.{ts,tsx}",
|
|
22
|
+
* "./node_modules/rl-core-front/src/**\/*.{ts,tsx}",
|
|
23
|
+
* ],
|
|
24
|
+
* } satisfies Config;
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
const preset: Omit<Config, 'content'> = {
|
|
28
|
+
darkMode: 'class',
|
|
29
|
+
theme: {
|
|
30
|
+
container: {
|
|
31
|
+
center: true,
|
|
32
|
+
padding: '2rem',
|
|
33
|
+
screens: { '2xl': '1400px' },
|
|
34
|
+
},
|
|
35
|
+
extend: {
|
|
36
|
+
colors: {
|
|
37
|
+
border: 'hsl(var(--border))',
|
|
38
|
+
input: 'hsl(var(--input))',
|
|
39
|
+
ring: 'hsl(var(--ring))',
|
|
40
|
+
background: 'hsl(var(--background))',
|
|
41
|
+
foreground: 'hsl(var(--foreground))',
|
|
42
|
+
primary: {
|
|
43
|
+
DEFAULT: 'hsl(var(--primary))',
|
|
44
|
+
foreground: 'hsl(var(--primary-foreground))',
|
|
45
|
+
},
|
|
46
|
+
secondary: {
|
|
47
|
+
DEFAULT: 'hsl(var(--secondary))',
|
|
48
|
+
foreground: 'hsl(var(--secondary-foreground))',
|
|
49
|
+
},
|
|
50
|
+
destructive: {
|
|
51
|
+
DEFAULT: 'hsl(var(--destructive))',
|
|
52
|
+
foreground: 'hsl(var(--destructive-foreground))',
|
|
53
|
+
},
|
|
54
|
+
muted: {
|
|
55
|
+
DEFAULT: 'hsl(var(--muted))',
|
|
56
|
+
foreground: 'hsl(var(--muted-foreground))',
|
|
57
|
+
},
|
|
58
|
+
accent: {
|
|
59
|
+
DEFAULT: 'hsl(var(--accent))',
|
|
60
|
+
foreground: 'hsl(var(--accent-foreground))',
|
|
61
|
+
},
|
|
62
|
+
popover: {
|
|
63
|
+
DEFAULT: 'hsl(var(--popover))',
|
|
64
|
+
foreground: 'hsl(var(--popover-foreground))',
|
|
65
|
+
},
|
|
66
|
+
card: {
|
|
67
|
+
DEFAULT: 'hsl(var(--card))',
|
|
68
|
+
foreground: 'hsl(var(--card-foreground))',
|
|
69
|
+
},
|
|
70
|
+
success: {
|
|
71
|
+
DEFAULT: 'hsl(var(--success))',
|
|
72
|
+
foreground: 'hsl(var(--success-foreground))',
|
|
73
|
+
},
|
|
74
|
+
warning: {
|
|
75
|
+
DEFAULT: 'hsl(var(--warning))',
|
|
76
|
+
foreground: 'hsl(var(--warning-foreground))',
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
borderRadius: {
|
|
80
|
+
lg: 'var(--radius)',
|
|
81
|
+
md: 'calc(var(--radius) - 2px)',
|
|
82
|
+
sm: 'calc(var(--radius) - 4px)',
|
|
83
|
+
},
|
|
84
|
+
keyframes: {
|
|
85
|
+
'accordion-down': {
|
|
86
|
+
from: { height: '0' },
|
|
87
|
+
to: { height: 'var(--radix-accordion-content-height)' },
|
|
88
|
+
},
|
|
89
|
+
'accordion-up': {
|
|
90
|
+
from: { height: 'var(--radix-accordion-content-height)' },
|
|
91
|
+
to: { height: '0' },
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
animation: {
|
|
95
|
+
'accordion-down': 'accordion-down 0.2s ease-out',
|
|
96
|
+
'accordion-up': 'accordion-up 0.2s ease-out',
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
plugins: [tailwindcssAnimate],
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
export default preset;
|