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
package/README.md ADDED
@@ -0,0 +1,129 @@
1
+ # rl-core-front
2
+
3
+ As telas do core, em Next.js. Par do [`rl-core-api`](https://www.npmjs.com/package/rl-core-api).
4
+
5
+ Login com 2FA, usuários, RBAC, auditoria, logs e perfil — mais os primitivos de
6
+ UI e a `DataTable` com paginação, ordenação e filtro dinâmico.
7
+
8
+ ```bash
9
+ npm install rl-core-front
10
+ ```
11
+
12
+ ## Instalação
13
+
14
+ O pacote é publicado em **código-fonte**, não compilado — são 72 arquivos com
15
+ `'use client'`, e compilar quebraria as diretivas. São três ajustes de
16
+ configuração, uma vez só.
17
+
18
+ **1. `next.config.ts`** — opta o pacote para dentro do compilador:
19
+
20
+ ```ts
21
+ const nextConfig: NextConfig = {
22
+ transpilePackages: ["rl-core-front"],
23
+ };
24
+ ```
25
+
26
+ **2. `tailwind.config.ts`** — o preset traz o tema; o `content` precisa varrer
27
+ o pacote, senão as classes usadas por ele são descartadas e a interface sai sem
28
+ estilo:
29
+
30
+ ```ts
31
+ import corePreset from "rl-core-front/tailwind-preset";
32
+
33
+ export default {
34
+ ...corePreset,
35
+ content: [
36
+ "./src/**/*.{ts,tsx}",
37
+ "./node_modules/rl-core-front/src/**/*.{ts,tsx}",
38
+ ],
39
+ } satisfies Config;
40
+ ```
41
+
42
+ **3. `globals.css`** — os tokens de cor:
43
+
44
+ ```css
45
+ @import "tailwindcss";
46
+ @config "../../tailwind.config.ts";
47
+ @import "rl-core-front/styles.css";
48
+ ```
49
+
50
+ ## Uso
51
+
52
+ Cada rota fica com uma linha:
53
+
54
+ ```tsx
55
+ // src/app/(app)/usuarios/page.tsx
56
+ export { UsersScreen as default } from "rl-core-front";
57
+ ```
58
+
59
+ O `layout.tsx` empilha os providers:
60
+
61
+ ```tsx
62
+ import {
63
+ AuthProvider,
64
+ ColorModeProvider,
65
+ I18nProvider,
66
+ SocketProvider,
67
+ ToastProvider,
68
+ } from "rl-core-front";
69
+
70
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
71
+ return (
72
+ <ColorModeProvider>
73
+ <I18nProvider>
74
+ <ToastProvider>
75
+ <AuthProvider>
76
+ <SocketProvider>{children}</SocketProvider>
77
+ </AuthProvider>
78
+ </ToastProvider>
79
+ </I18nProvider>
80
+ </ColorModeProvider>
81
+ );
82
+ }
83
+ ```
84
+
85
+ E o `middleware.ts` da raiz:
86
+
87
+ ```ts
88
+ export { config, middleware } from "rl-core-front/middleware";
89
+ ```
90
+
91
+ ## Nas suas telas
92
+
93
+ Os primitivos e hooks são os mesmos que as telas do core usam:
94
+
95
+ ```tsx
96
+ import { Button, DataTable, useListQuery } from "rl-core-front";
97
+
98
+ export function ContasScreen() {
99
+ const query = useListQuery("/contas/filter-schema");
100
+ return <DataTable columns={columns} {...query} />;
101
+ }
102
+ ```
103
+
104
+ `useListQuery` cuida de paginação, ordenação e filtro — inclusive o painel
105
+ lateral, os chips e a querystring, que sobrevive ao F5 e deixa o link
106
+ compartilhável. O catálogo de campos vem do backend; você não monta formulário
107
+ de filtro à mão.
108
+
109
+ ## Traduções
110
+
111
+ As mensagens do core saem prontas e você mescla as suas:
112
+
113
+ ```ts
114
+ import { en as coreEn, pt as corePt } from "rl-core-front";
115
+
116
+ export const messages = {
117
+ pt: { ...corePt, ...appPt },
118
+ en: { ...coreEn, ...appEn },
119
+ };
120
+ ```
121
+
122
+ ## Peer dependencies
123
+
124
+ `next`, `react`, `react-dom` e `tailwindcss` — precisam ser uma instância só.
125
+ O npm 7+ instala sozinho.
126
+
127
+ ## Licença
128
+
129
+ MIT © Rodrigo Liberti
package/package.json ADDED
@@ -0,0 +1,113 @@
1
+ {
2
+ "name": "rl-core-front",
3
+ "version": "0.1.0",
4
+ "description": "Telas e componentes Next.js do core: login com 2FA, usu\u00e1rios, RBAC, auditoria, logs e listagens com filtro din\u00e2mico",
5
+ "author": "Rodrigo Liberti",
6
+ "license": "MIT",
7
+ "keywords": [
8
+ "nextjs",
9
+ "react",
10
+ "tailwind",
11
+ "auth",
12
+ "2fa",
13
+ "rbac",
14
+ "data-table",
15
+ "boilerplate"
16
+ ],
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/liberti1991/aplication-core.git",
20
+ "directory": "packages/front"
21
+ },
22
+ "homepage": "https://github.com/liberti1991/aplication-core#readme",
23
+ "bugs": "https://github.com/liberti1991/aplication-core/issues",
24
+ "imports": {
25
+ "#core/*": "./src/*"
26
+ },
27
+ "exports": {
28
+ ".": "./src/index.ts",
29
+ "./middleware": "./src/middleware.ts",
30
+ "./styles.css": "./src/styles/core.css",
31
+ "./tailwind-preset": "./tailwind-preset.ts",
32
+ "./package.json": "./package.json"
33
+ },
34
+ "files": [
35
+ "src",
36
+ "!src/app",
37
+ "!src/**/*.test.ts",
38
+ "!src/**/*.test.tsx",
39
+ "tailwind-preset.ts",
40
+ "README.md"
41
+ ],
42
+ "publishConfig": {
43
+ "access": "public"
44
+ },
45
+ "scripts": {
46
+ "dev": "next dev -p 4000",
47
+ "build": "next build",
48
+ "prepublishOnly": "npm run lint && npx tsc --noEmit && npm test && npm run build",
49
+ "start": "next start -p 4000",
50
+ "lint": "eslint .",
51
+ "lint:fix": "eslint . --fix",
52
+ "openapi:types": "openapi-typescript ../../openapi.json -o src/_services/api/schema.d.ts",
53
+ "test": "vitest run",
54
+ "test:watch": "vitest"
55
+ },
56
+ "peerDependencies": {
57
+ "next": "^16.3.1",
58
+ "react": "^19.2.8",
59
+ "react-dom": "^19.2.8",
60
+ "tailwindcss": "^4.3.3"
61
+ },
62
+ "dependencies": {
63
+ "@hookform/resolvers": "^5.7.1",
64
+ "@radix-ui/react-avatar": "^1.2.6",
65
+ "@radix-ui/react-checkbox": "^1.3.11",
66
+ "@radix-ui/react-dialog": "^1.1.23",
67
+ "@radix-ui/react-dropdown-menu": "^2.1.24",
68
+ "@radix-ui/react-label": "^2.1.15",
69
+ "@radix-ui/react-select": "^2.3.7",
70
+ "@radix-ui/react-separator": "^1.1.15",
71
+ "@radix-ui/react-slot": "^1.3.3",
72
+ "@radix-ui/react-switch": "^1.3.7",
73
+ "@radix-ui/react-tabs": "^1.1.21",
74
+ "@tanstack/react-table": "^9.1.0",
75
+ "axios": "^1.19.0",
76
+ "class-variance-authority": "^0.7.1",
77
+ "clsx": "^2.1.1",
78
+ "lucide-react": "^1.30.0",
79
+ "react-easy-crop": "^6.2.3",
80
+ "react-hook-form": "^7.85.0",
81
+ "socket.io-client": "^4.8.3",
82
+ "sonner": "^2.0.7",
83
+ "tailwind-merge": "^3.6.0",
84
+ "tailwindcss-animate": "^1.0.7",
85
+ "yup": "^1.7.1"
86
+ },
87
+ "devDependencies": {
88
+ "@eslint/eslintrc": "^3.3.6",
89
+ "@tailwindcss/postcss": "^4.3.3",
90
+ "@testing-library/dom": "^10.4.1",
91
+ "@testing-library/react": "^16.3.2",
92
+ "@types/node": "^26.2.0",
93
+ "@types/react": "^19.2.18",
94
+ "@types/react-dom": "^19.2.4",
95
+ "@vitejs/plugin-react": "^6.0.5",
96
+ "autoprefixer": "^10.5.4",
97
+ "eslint": "^10.8.1",
98
+ "eslint-config-next": "^16.3.1",
99
+ "eslint-plugin-simple-import-sort": "^14.0.0",
100
+ "jsdom": "^30.0.1",
101
+ "next": "^16.3.1",
102
+ "openapi-typescript": "^7.13.0",
103
+ "postcss": "^8.5.26",
104
+ "react": "^19.2.8",
105
+ "react-dom": "^19.2.8",
106
+ "tailwindcss": "^4.3.3",
107
+ "typescript": "~6.0.3",
108
+ "vitest": "^4.1.10"
109
+ },
110
+ "engines": {
111
+ "node": ">=20"
112
+ }
113
+ }
@@ -0,0 +1,97 @@
1
+ import axios, {
2
+ AxiosError,
3
+ AxiosInstance,
4
+ InternalAxiosRequestConfig,
5
+ } from "axios";
6
+
7
+ const BASE_URL =
8
+ process.env.NEXT_PUBLIC_API_BASE_URL ?? "http://localhost:3108/api/v1";
9
+
10
+ const PUBLIC_PATHS = ["/login", "/forgot-password", "/reset-password"];
11
+
12
+ function isPublicPath(pathname: string): boolean {
13
+ return PUBLIC_PATHS.some((p) => pathname.startsWith(p));
14
+ }
15
+
16
+ /**
17
+ * Cria uma instância axios com:
18
+ * - withCredentials (envia/recebe cookies httpOnly de auth);
19
+ * - refresh automático e único em caso de 401 (rotaciona o refresh token).
20
+ */
21
+ export class AxiosFactory {
22
+ private isRefreshing = false;
23
+ private queue: Array<(ok: boolean) => void> = [];
24
+
25
+ make(): AxiosInstance {
26
+ const api = axios.create({
27
+ baseURL: BASE_URL,
28
+ withCredentials: true,
29
+ headers: { "Content-Type": "application/json" },
30
+ });
31
+
32
+ api.interceptors.response.use(
33
+ (res) => res,
34
+ async (error: AxiosError) => {
35
+ const original = error.config as InternalAxiosRequestConfig & {
36
+ _retry?: boolean;
37
+ };
38
+ const status = error.response?.status;
39
+ const url = original?.url ?? "";
40
+
41
+ const isAuthRoute =
42
+ url.includes("/auth/login") ||
43
+ url.includes("/auth/refresh") ||
44
+ url.includes("/auth/2fa");
45
+
46
+ if (status === 401 && !original?._retry && !isAuthRoute) {
47
+ original._retry = true;
48
+ const refreshed = await this.refresh(api);
49
+ if (refreshed) {
50
+ return api(original);
51
+ }
52
+ if (
53
+ typeof window !== "undefined" &&
54
+ !isPublicPath(window.location.pathname)
55
+ ) {
56
+ // Limpa os cookies httpOnly no servidor antes de redirecionar — sem isso o
57
+ // middleware ainda vê os cookies (inválidos, mas presentes) e manda de volta
58
+ // pro dashboard, gerando loop entre /dashboard e /login?reason=expired.
59
+ await api.post("/auth/logout").catch(() => {});
60
+ window.location.href = "/login?reason=expired";
61
+ }
62
+ }
63
+ return Promise.reject(error);
64
+ },
65
+ );
66
+
67
+ return api;
68
+ }
69
+
70
+ private refresh(api: AxiosInstance): Promise<boolean> {
71
+ if (this.isRefreshing) {
72
+ return new Promise((resolve) => this.queue.push(resolve));
73
+ }
74
+ this.isRefreshing = true;
75
+ return api
76
+ .post("/auth/refresh")
77
+ .then(() => {
78
+ this.flush(true);
79
+ return true;
80
+ })
81
+ .catch(() => {
82
+ this.flush(false);
83
+ return false;
84
+ })
85
+ .finally(() => {
86
+ this.isRefreshing = false;
87
+ });
88
+ }
89
+
90
+ private flush(ok: boolean): void {
91
+ this.queue.forEach((cb) => cb(ok));
92
+ this.queue = [];
93
+ }
94
+ }
95
+
96
+ /** Instância única reutilizável da API. */
97
+ export const api = new AxiosFactory().make();
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Query de uma listagem paginada, como a tabela a enxerga.
3
+ *
4
+ * Existe para os `.service.ts` pararem de crescer em parâmetros posicionais —
5
+ * eram cinco e o filtro seria o sexto.
6
+ */
7
+ export interface ListQuery {
8
+ /** Base 0, como o `PaginationState` da tabela. */
9
+ page: number;
10
+ limit: number;
11
+ search?: string;
12
+ sortBy?: string;
13
+ sortDir?: "ASC" | "DESC";
14
+ /** JSON do filtro dinâmico, montado pelo `useFilters`. */
15
+ filter?: string;
16
+ }
17
+
18
+ /** `ListQuery` → params do axios. A API pagina a partir de 1. */
19
+ export const toListParams = (query: ListQuery): Record<string, unknown> => ({
20
+ page: query.page + 1,
21
+ limit: query.limit,
22
+ search: query.search || undefined,
23
+ sortBy: query.sortBy,
24
+ sortDir: query.sortDir,
25
+ filter: query.filter,
26
+ });