rbac-manager 0.0.0-stage → 1.0.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/.github/workflows/publish.yml +29 -0
- package/README.md +317 -2
- package/package.json +45 -4
- package/scripts/postbuild.js +17 -0
- package/src/config/rbac.config.ts +206 -0
- package/src/constants/actions.constants.ts +21 -0
- package/src/constants/apiKeys.constants.ts +25 -0
- package/src/constants/auth.constants.ts +39 -0
- package/src/constants/endpoints.constants.ts +66 -0
- package/src/constants/i18n.constants.ts +82 -0
- package/src/constants/index.ts +70 -0
- package/src/constants/masters.constants.ts +68 -0
- package/src/constants/permissions/index.ts +44 -0
- package/src/constants/permissions/masterPermissions.constants.ts +44 -0
- package/src/constants/permissions/rolePermissions.constants.ts +18 -0
- package/src/constants/permissions.constants.ts +34 -0
- package/src/constants/roles.constants.ts +34 -0
- package/src/core/adapters/authAdapter.ts +89 -0
- package/src/core/adapters/index.ts +17 -0
- package/src/core/adapters/mastersAdapter.ts +132 -0
- package/src/core/adapters/permissionsAdapter.ts +90 -0
- package/src/core/adapters/rolesAdapter.ts +115 -0
- package/src/core/api/httpClient.ts +296 -0
- package/src/core/context/PermissionContext.tsx +232 -0
- package/src/core/context/RBACConfigContext.tsx +52 -0
- package/src/core/guards/Can.tsx +98 -0
- package/src/core/guards/RouteGuard.tsx +42 -0
- package/src/core/hooks/useMaster.ts +99 -0
- package/src/core/hooks/usePermissions.ts +114 -0
- package/src/core/hooks/useRoles.ts +120 -0
- package/src/core/types/adapter.types.ts +54 -0
- package/src/core/types/auth.types.ts +44 -0
- package/src/core/types/index.ts +7 -0
- package/src/core/types/masters.types.ts +63 -0
- package/src/core/types/pagination.types.ts +37 -0
- package/src/core/types/rbac.types.ts +69 -0
- package/src/core/types/router.types.ts +43 -0
- package/src/core/types/style.types.ts +53 -0
- package/src/core/utils/permissionResolver.ts +44 -0
- package/src/index.ts +44 -0
- package/src/modules/masters/DepartmentMasterView.tsx +126 -0
- package/src/modules/masters/DesignationMasterView.tsx +97 -0
- package/src/modules/masters/EmployeeMasterView.tsx +107 -0
- package/src/modules/masters/EntityMasterView.tsx +102 -0
- package/src/modules/masters/ModuleMasterView.tsx +104 -0
- package/src/modules/masters/SubModuleMasterView.tsx +106 -0
- package/src/modules/masters/components/MasterDataTable.tsx +684 -0
- package/src/modules/masters/index.ts +7 -0
- package/src/modules/roles-and-permissions/components/PermissionMatrix.tsx +289 -0
- package/src/modules/roles-and-permissions/components/PermissionRegistry.tsx +108 -0
- package/src/modules/roles-and-permissions/components/RoleListTable.tsx +568 -0
- package/src/modules/roles-and-permissions/components/RoleModal.tsx +213 -0
- package/src/modules/roles-and-permissions/index.ts +4 -0
- package/src/shared/components/Badge.tsx +24 -0
- package/src/shared/components/Button.tsx +75 -0
- package/src/shared/components/DynamicSidebar.tsx +181 -0
- package/src/shared/components/Input.tsx +48 -0
- package/src/shared/components/Modal.tsx +70 -0
- package/src/shared/components/Table.tsx +345 -0
- package/src/styles/standalone.css +51 -0
- package/src/theme/ThemeContext.tsx +74 -0
- package/src/theme/defaultTheme.ts +81 -0
- package/src/theme/index.ts +3 -0
- package/src/theme/theme.types.ts +106 -0
- package/tsconfig.cjs.json +10 -0
- package/tsconfig.esm.json +10 -0
- package/tsconfig.json +28 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
name: Publish to npm
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
id-token: write
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
publish:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- uses: actions/setup-node@v4
|
|
19
|
+
with:
|
|
20
|
+
node-version: 22
|
|
21
|
+
registry-url: https://registry.npmjs.org
|
|
22
|
+
|
|
23
|
+
# npm's trusted publishing requires a recent npm CLI.
|
|
24
|
+
- run: npm install --global npm@latest
|
|
25
|
+
|
|
26
|
+
- run: npm ci
|
|
27
|
+
- run: npm run typecheck
|
|
28
|
+
- run: npm run build
|
|
29
|
+
- run: npm publish --access public --provenance
|
package/README.md
CHANGED
|
@@ -1,3 +1,318 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Universal Enterprise RBAC & Masters Template
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A production-ready, highly modular, frontend-only RBAC (Role-Based Access Control) and Masters framework built for React and Next.js. Engineered to connect directly to any backend REST API with **zero hardcoded keys**, **dynamic permission naming & aliases**, **pluggable API adapters**, **granular action toggles**, **dynamic table schemas**, and **built-in solutions for all enterprise integration roadblocks**.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 🌟 Core Highlights
|
|
8
|
+
|
|
9
|
+
- **Frontend-Only**: No database or backend code required. Directly interfaces with backend API endpoints.
|
|
10
|
+
- **Universal Framework Routing**: Native adapters for Next.js App Router (`useRouter`), React Router (`useNavigate`, `Link`), TanStack Router, and Remix—eliminating full-page browser reloads.
|
|
11
|
+
- **Zero FOUC (Flash of Unauthorized Content)**: Full SSR session hydration support (`initialUser`, `initialPermissions`) prevents permission flashing on server-rendered pages.
|
|
12
|
+
- **Silent Token Refresh & Request Replay**: Transparently catches 401s, calls your refresh token API, and replays failed requests without logging the user out.
|
|
13
|
+
- **Multi-Tenancy & Workspace Switching**: Built-in `activeTenantId` and `setTenantId()` automatically injects `X-Tenant-Id` headers and re-fetches permissions on the fly.
|
|
14
|
+
- **Flexible Pagination Engine**: Seamlessly adapts to 0-based (`page=0`), 1-based (`page=1`), or SQL offset/limit (`offset=0&limit=10`) backend contracts.
|
|
15
|
+
- **Cascading Form Dropdowns**: Dynamic select options that filter automatically based on parent fields (e.g. Department list dynamically filtered by selected Entity).
|
|
16
|
+
- **Zero Hardcoded Permission Strings**: All permission names are isolated in module-level constants (`src/constants/permissions/`).
|
|
17
|
+
- **Supports Any Permission Naming Convention**: Whether your backend sends `roles.add`, `roles.addition`, `roles:create`, or `ROLE_ADD`, it matches automatically via the dynamic permission resolver and alias map.
|
|
18
|
+
- **100% Complete Style & ClassName Overrides**: Strip default styles with `unstyled={true}` or inject custom Tailwind/CSS classes into any sub-element.
|
|
19
|
+
- **Dual ESM & CommonJS**: Ships with first-class ES modules (`dist/esm`) and CommonJS (`dist/cjs`) bundles with full TypeScript `.d.ts` declarations.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## 🛠️ Solutions for Enterprise Integration Roadblocks
|
|
24
|
+
|
|
25
|
+
### 1. Framework Routing (Next.js & React Router)
|
|
26
|
+
Avoid full-page reloads by passing your framework's native router:
|
|
27
|
+
|
|
28
|
+
```tsx
|
|
29
|
+
// Next.js App Router:
|
|
30
|
+
import { useRouter } from 'next/navigation';
|
|
31
|
+
import Link from 'next/link';
|
|
32
|
+
import { createNextRouterAdapter, createRBACConfig } from '@custom/rbac-template';
|
|
33
|
+
|
|
34
|
+
const rbacConfig = createRBACConfig({
|
|
35
|
+
apiBaseUrl: 'https://api.example.com',
|
|
36
|
+
router: createNextRouterAdapter(useRouter(), Link),
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// React Router (Vite / CRA):
|
|
40
|
+
import { useNavigate, Link } from 'react-router-dom';
|
|
41
|
+
import { createReactRouterAdapter } from '@custom/rbac-template';
|
|
42
|
+
|
|
43
|
+
const rbacConfig = createRBACConfig({
|
|
44
|
+
router: createReactRouterAdapter(useNavigate(), Link),
|
|
45
|
+
});
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
### 2. Eliminating SSR Flashing (Zero FOUC)
|
|
51
|
+
In Next.js, read your session on the server and pass it directly to `PermissionProvider`. The page renders with full permissions immediately upon hydration:
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
// app/layout.tsx (Server Component)
|
|
55
|
+
import { getServerSession } from '@/lib/auth';
|
|
56
|
+
import { ClientProviders } from './providers';
|
|
57
|
+
|
|
58
|
+
export default async function RootLayout({ children }) {
|
|
59
|
+
const session = await getServerSession();
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<html>
|
|
63
|
+
<body>
|
|
64
|
+
<ClientProviders
|
|
65
|
+
initialUser={session?.user}
|
|
66
|
+
initialPermissions={session?.permissions}
|
|
67
|
+
initialNavigation={session?.navigation}
|
|
68
|
+
>
|
|
69
|
+
{children}
|
|
70
|
+
</ClientProviders>
|
|
71
|
+
</body>
|
|
72
|
+
</html>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
### 3. Silent Token Refresh (401 Interception)
|
|
80
|
+
Prevent users from losing form progress due to expired access tokens:
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
const rbacConfig = createRBACConfig({
|
|
84
|
+
apiBaseUrl: 'https://api.example.com',
|
|
85
|
+
getToken: () => localStorage.getItem('accessToken'),
|
|
86
|
+
// Catches 401s, refreshes token, and replays failed requests transparently:
|
|
87
|
+
refreshToken: async () => {
|
|
88
|
+
const res = await fetch('/api/auth/refresh', { method: 'POST' });
|
|
89
|
+
const data = await res.json();
|
|
90
|
+
localStorage.setItem('accessToken', data.accessToken);
|
|
91
|
+
return data.accessToken;
|
|
92
|
+
},
|
|
93
|
+
onUnauthorized: () => {
|
|
94
|
+
window.location.href = '/login';
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
### 4. Multi-Tenancy & Entity Switching
|
|
102
|
+
Switch active company/branch workspaces and automatically update permissions:
|
|
103
|
+
|
|
104
|
+
```tsx
|
|
105
|
+
import { usePermission } from '@custom/rbac-template';
|
|
106
|
+
|
|
107
|
+
export function TenantSwitcher({ entities }) {
|
|
108
|
+
const { activeTenantId, setTenantId } = usePermission();
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
<select
|
|
112
|
+
value={activeTenantId || ''}
|
|
113
|
+
onChange={(e) => setTenantId(e.target.value)} // Automatically refetches /auth/me & adds X-Tenant-Id header
|
|
114
|
+
>
|
|
115
|
+
{entities.map((e) => (
|
|
116
|
+
<option key={e.id} value={e.id}>
|
|
117
|
+
{e.name}
|
|
118
|
+
</option>
|
|
119
|
+
))}
|
|
120
|
+
</select>
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
### 5. Backend Pagination Contract Adaptation
|
|
128
|
+
Configure 0-based vs 1-based indexing or offset/limit mode in 1 line:
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
const rbacConfig = createRBACConfig({
|
|
132
|
+
pagination: {
|
|
133
|
+
pageIndexOffset: 0, // For Spring Boot (0-based indexing: page=0)
|
|
134
|
+
pageParamName: 'pageIndex',
|
|
135
|
+
limitParamName: 'pageSize',
|
|
136
|
+
// Or for SQL offset/limit:
|
|
137
|
+
// useOffsetLimit: true,
|
|
138
|
+
// offsetParamName: 'offset',
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
### 6. Cascading / Dependent Form Dropdowns
|
|
146
|
+
Configure form fields to dynamically filter based on parent selections:
|
|
147
|
+
|
|
148
|
+
```tsx
|
|
149
|
+
<EmployeeMasterView
|
|
150
|
+
formFields={[
|
|
151
|
+
{ name: 'firstName', label: 'First Name', required: true },
|
|
152
|
+
{
|
|
153
|
+
name: 'entityId',
|
|
154
|
+
label: 'Entity',
|
|
155
|
+
type: 'select',
|
|
156
|
+
options: entities.map((e) => ({ label: e.name, value: e.id })),
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
name: 'departmentId',
|
|
160
|
+
label: 'Department',
|
|
161
|
+
type: 'select',
|
|
162
|
+
dependsOn: ['entityId'], // Re-evaluates when entityId changes
|
|
163
|
+
options: (formData) =>
|
|
164
|
+
departments
|
|
165
|
+
.filter((d) => !formData.entityId || d.entityId === formData.entityId)
|
|
166
|
+
.map((d) => ({ label: d.name, value: d.id })),
|
|
167
|
+
disabled: (formData) => !formData.entityId, // Disabled until entity is chosen
|
|
168
|
+
},
|
|
169
|
+
]}
|
|
170
|
+
/>
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
### 7. Dynamic Permission Names & Aliases (`roles.add` vs `roles.addition`)
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
const rbacConfig = createRBACConfig({
|
|
179
|
+
permissionAliases: {
|
|
180
|
+
'roles.add': 'roles.addition', // Maps 'roles.add' -> 'roles.addition'
|
|
181
|
+
'roles.edit': 'roles:modify', // Colon notation support
|
|
182
|
+
'department_master.add': 'dept.create', // Custom department create slug
|
|
183
|
+
},
|
|
184
|
+
// Or global format resolver:
|
|
185
|
+
// permissionResolver: (module, action) => `${module}:${action}`,
|
|
186
|
+
});
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
### 8. Field-Level Validation Error Mapping
|
|
192
|
+
When your backend validation fails (e.g. HTTP 400 or 422 with `{ errors: { name: "Name is already taken" } }` or `{ errors: [{ field: "code", message: "Duplicate" }] }`), `HttpClient` and `MasterDataTable` automatically normalize and highlight each field directly under its input element.
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
### 9. Toast & Notification Library Callbacks
|
|
197
|
+
Connect any UI toast library (Sonner, react-toastify, AntD, Chakra) with zero vendor lock-in:
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
import { toast } from 'sonner';
|
|
201
|
+
|
|
202
|
+
const rbacConfig = createRBACConfig({
|
|
203
|
+
onNotification: ({ type, title, message }) => {
|
|
204
|
+
if (type === 'success') toast.success(message);
|
|
205
|
+
else if (type === 'error') toast.error(message);
|
|
206
|
+
else toast(message);
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
### 10. URL Query State Synchronization (Deep Linking)
|
|
214
|
+
Keep table filters, search keywords, page index, and sort order in sync with browser query params so users can bookmark and share URLs:
|
|
215
|
+
|
|
216
|
+
```tsx
|
|
217
|
+
<DepartmentMasterView syncWithUrl={true} defaultSortBy="name" defaultSortOrder="asc" />
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
### 11. Column Sorting (Ascending / Descending)
|
|
223
|
+
Mark any column as sortable with custom backend keys and direction toggle indicators:
|
|
224
|
+
|
|
225
|
+
```tsx
|
|
226
|
+
<Table
|
|
227
|
+
columns={[
|
|
228
|
+
{ key: 'name', header: 'Department Name', sortable: true, sortKey: 'dept_name' },
|
|
229
|
+
{ key: 'createdAt', header: 'Created At', sortable: true },
|
|
230
|
+
]}
|
|
231
|
+
sortBy={sortBy}
|
|
232
|
+
sortOrder={sortOrder}
|
|
233
|
+
onSortChange={(colKey, direction) => handleSort(colKey, direction)}
|
|
234
|
+
/>
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
### 12. Bulk Row Selection & Batch Actions
|
|
240
|
+
Opt-in multi-row select checkboxes with floating batch action toolbars:
|
|
241
|
+
|
|
242
|
+
```tsx
|
|
243
|
+
<DepartmentMasterView
|
|
244
|
+
selectable={true}
|
|
245
|
+
batchActions={[
|
|
246
|
+
{
|
|
247
|
+
key: 'export',
|
|
248
|
+
label: 'Export Selected',
|
|
249
|
+
onClick: (selectedIds, selectedItems) => exportToCsv(selectedItems),
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
key: 'archive',
|
|
253
|
+
label: 'Archive',
|
|
254
|
+
variant: 'danger',
|
|
255
|
+
onClick: (selectedIds) => archiveDepartments(selectedIds),
|
|
256
|
+
},
|
|
257
|
+
]}
|
|
258
|
+
/>
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
### 13. Multi-Language i18n Localization
|
|
264
|
+
Translate or customize all table buttons, modals, badges, and counters:
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
const rbacConfig = createRBACConfig({
|
|
268
|
+
i18n: {
|
|
269
|
+
addNew: '+ Añadir',
|
|
270
|
+
saveChanges: 'Guardar cambios',
|
|
271
|
+
cancel: 'Cancelar',
|
|
272
|
+
delete: 'Eliminar',
|
|
273
|
+
searchPlaceholder: 'Buscar...',
|
|
274
|
+
showingEntries: (from, to, total) => `Mostrando ${from} a ${to} de ${total} registros`,
|
|
275
|
+
},
|
|
276
|
+
});
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
### 14. File & Multipart Uploads (`FormData`)
|
|
282
|
+
`HttpClient` automatically detects `FormData` instances, omitting default JSON headers so the browser dynamically injects the appropriate `multipart/form-data; boundary=...`:
|
|
283
|
+
|
|
284
|
+
```typescript
|
|
285
|
+
const formData = new FormData();
|
|
286
|
+
formData.append('file', file);
|
|
287
|
+
await http.upload('/masters/departments/import', formData);
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
## 🎨 Complete Style & ClassName Overrides
|
|
293
|
+
|
|
294
|
+
Pass custom CSS / Tailwind classes directly to any element of the module:
|
|
295
|
+
```tsx
|
|
296
|
+
<DepartmentMasterView
|
|
297
|
+
classNames={{
|
|
298
|
+
root: 'p-6 bg-slate-900 text-white rounded-2xl',
|
|
299
|
+
header: {
|
|
300
|
+
title: 'text-2xl font-bold text-amber-400',
|
|
301
|
+
addButton: 'bg-amber-500 hover:bg-amber-600 text-black',
|
|
302
|
+
},
|
|
303
|
+
table: {
|
|
304
|
+
thead: 'bg-slate-800 text-slate-300 font-semibold',
|
|
305
|
+
th: 'px-6 py-4 uppercase tracking-wider text-xs',
|
|
306
|
+
tr: 'hover:bg-slate-800/50 border-b border-slate-800',
|
|
307
|
+
td: 'px-6 py-4 text-slate-300',
|
|
308
|
+
},
|
|
309
|
+
}}
|
|
310
|
+
/>
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
Or pass `unstyled={true}` to strip all default styling when using **shadcn/ui**, **MUI**, or **Bootstrap**.
|
|
314
|
+
|
|
315
|
+
---
|
|
316
|
+
|
|
317
|
+
## 📄 License
|
|
318
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,47 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rbac-manager",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Enterprise-grade, highly customizable, frontend-only RBAC and Masters template with pluggable API adapters",
|
|
5
|
+
"main": "./dist/cjs/index.js",
|
|
6
|
+
"module": "./dist/esm/index.js",
|
|
7
|
+
"types": "./dist/esm/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/esm/index.d.ts",
|
|
11
|
+
"import": "./dist/esm/index.js",
|
|
12
|
+
"require": "./dist/cjs/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./styles.css": "./src/styles/standalone.css"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build:esm": "tsc -p tsconfig.esm.json",
|
|
18
|
+
"build:cjs": "tsc -p tsconfig.cjs.json",
|
|
19
|
+
"build": "npm run build:esm && npm run build:cjs && node scripts/postbuild.js",
|
|
20
|
+
"typecheck": "tsc --noEmit"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"rbac",
|
|
24
|
+
"permission",
|
|
25
|
+
"roles",
|
|
26
|
+
"masters",
|
|
27
|
+
"access-control",
|
|
28
|
+
"react",
|
|
29
|
+
"frontend-template"
|
|
30
|
+
],
|
|
31
|
+
"author": "",
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"react": ">=18.0.0",
|
|
35
|
+
"react-dom": ">=18.0.0"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@types/node": "^20.11.0",
|
|
39
|
+
"@types/react": "^18.3.31",
|
|
40
|
+
"@types/react-dom": "^18.3.7",
|
|
41
|
+
"typescript": "^5.9.3"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"clsx": "^2.1.0",
|
|
45
|
+
"tailwind-merge": "^2.2.0"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const cjsPackageJson = path.join(__dirname, '..', 'dist', 'cjs', 'package.json');
|
|
5
|
+
const esmPackageJson = path.join(__dirname, '..', 'dist', 'esm', 'package.json');
|
|
6
|
+
|
|
7
|
+
// Ensure dist/cjs exists
|
|
8
|
+
if (fs.existsSync(path.dirname(cjsPackageJson))) {
|
|
9
|
+
fs.writeFileSync(cjsPackageJson, JSON.stringify({ type: 'commonjs' }, null, 2));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Ensure dist/esm exists
|
|
13
|
+
if (fs.existsSync(path.dirname(esmPackageJson))) {
|
|
14
|
+
fs.writeFileSync(esmPackageJson, JSON.stringify({ type: 'module' }, null, 2));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
console.log('Postbuild: Successfully generated CJS and ESM package.json manifests.');
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { RBACAdapters } from '../core/types/adapter.types';
|
|
2
|
+
import { DEFAULT_ADAPTERS } from '../core/adapters';
|
|
3
|
+
import { RBAC_CONSTANTS, configureRBACConstants, RBACLocaleStrings } from '../constants';
|
|
4
|
+
import { configurePermissionKeys } from '../constants/permissions';
|
|
5
|
+
import { RBACTheme, defaultRBACTheme, mergeThemes } from '../theme';
|
|
6
|
+
import { PermissionKeyResolverFn, defaultPermissionResolver } from '../core/utils/permissionResolver';
|
|
7
|
+
import { RouterAdapter } from '../core/types/router.types';
|
|
8
|
+
import { PaginationConfig, defaultPaginationConfig } from '../core/types/pagination.types';
|
|
9
|
+
|
|
10
|
+
export interface RBACNotification {
|
|
11
|
+
type: 'success' | 'error' | 'info' | 'warning';
|
|
12
|
+
title?: string;
|
|
13
|
+
message: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type RBACNotificationHandler = (notification: RBACNotification) => void;
|
|
17
|
+
|
|
18
|
+
export interface ModuleFeatureConfig {
|
|
19
|
+
enabled?: boolean;
|
|
20
|
+
allowAdd?: boolean;
|
|
21
|
+
allowEdit?: boolean;
|
|
22
|
+
allowDelete?: boolean;
|
|
23
|
+
allowView?: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type FeatureToggle = boolean | ModuleFeatureConfig;
|
|
27
|
+
|
|
28
|
+
export interface RBACFeaturesConfig {
|
|
29
|
+
masters: {
|
|
30
|
+
entity: FeatureToggle;
|
|
31
|
+
department: FeatureToggle;
|
|
32
|
+
designation: FeatureToggle;
|
|
33
|
+
employee: FeatureToggle;
|
|
34
|
+
moduleMaster: FeatureToggle;
|
|
35
|
+
subModuleMaster: FeatureToggle;
|
|
36
|
+
[customMaster: string]: FeatureToggle | undefined;
|
|
37
|
+
};
|
|
38
|
+
rolesAndPermissions: {
|
|
39
|
+
roleCrud: FeatureToggle;
|
|
40
|
+
permissionAssignmentMatrix: boolean;
|
|
41
|
+
permissionRegistry: boolean;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface RBACConfig {
|
|
46
|
+
apiBaseUrl: string;
|
|
47
|
+
features: RBACFeaturesConfig;
|
|
48
|
+
endpoints: typeof RBAC_CONSTANTS.endpoints;
|
|
49
|
+
adapters: RBACAdapters;
|
|
50
|
+
theme: RBACTheme;
|
|
51
|
+
router?: RouterAdapter;
|
|
52
|
+
pagination: PaginationConfig;
|
|
53
|
+
permissionResolver: PermissionKeyResolverFn;
|
|
54
|
+
permissionAliases: Record<string, string>;
|
|
55
|
+
i18n?: Partial<RBACLocaleStrings>;
|
|
56
|
+
onNotification?: RBACNotificationHandler;
|
|
57
|
+
getToken?: () => string | null | undefined;
|
|
58
|
+
getTenantId?: () => string | number | null | undefined;
|
|
59
|
+
getCustomHeaders?: () => Record<string, string>;
|
|
60
|
+
refreshToken?: () => Promise<string | null | undefined>;
|
|
61
|
+
onUnauthorized?: () => void;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export const defaultRBACConfig: RBACConfig = {
|
|
65
|
+
apiBaseUrl: 'https://api.example.com',
|
|
66
|
+
features: {
|
|
67
|
+
masters: {
|
|
68
|
+
entity: { enabled: true, allowAdd: true, allowEdit: true, allowDelete: true },
|
|
69
|
+
department: { enabled: true, allowAdd: true, allowEdit: true, allowDelete: true },
|
|
70
|
+
designation: { enabled: true, allowAdd: true, allowEdit: true, allowDelete: true },
|
|
71
|
+
employee: { enabled: true, allowAdd: true, allowEdit: true, allowDelete: true },
|
|
72
|
+
moduleMaster: { enabled: true, allowAdd: true, allowEdit: true, allowDelete: true },
|
|
73
|
+
subModuleMaster: { enabled: true, allowAdd: true, allowEdit: true, allowDelete: true },
|
|
74
|
+
},
|
|
75
|
+
rolesAndPermissions: {
|
|
76
|
+
roleCrud: { enabled: true, allowAdd: true, allowEdit: true, allowDelete: true },
|
|
77
|
+
permissionAssignmentMatrix: true,
|
|
78
|
+
permissionRegistry: true,
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
endpoints: { ...RBAC_CONSTANTS.endpoints },
|
|
82
|
+
adapters: { ...DEFAULT_ADAPTERS },
|
|
83
|
+
theme: defaultRBACTheme,
|
|
84
|
+
pagination: defaultPaginationConfig,
|
|
85
|
+
permissionResolver: defaultPermissionResolver,
|
|
86
|
+
permissionAliases: {},
|
|
87
|
+
getToken: () => {
|
|
88
|
+
if (typeof window !== 'undefined') {
|
|
89
|
+
return localStorage.getItem('token') || undefined;
|
|
90
|
+
}
|
|
91
|
+
return undefined;
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Check if a specific CRUD action is allowed for a given feature toggle
|
|
97
|
+
*/
|
|
98
|
+
export function isActionAllowed(
|
|
99
|
+
configValue: FeatureToggle | undefined,
|
|
100
|
+
action: 'add' | 'edit' | 'delete' | 'view'
|
|
101
|
+
): boolean {
|
|
102
|
+
if (configValue === undefined || configValue === false) return false;
|
|
103
|
+
if (configValue === true) return true;
|
|
104
|
+
if (configValue.enabled === false) return false;
|
|
105
|
+
if (action === 'add') return configValue.allowAdd ?? true;
|
|
106
|
+
if (action === 'edit') return configValue.allowEdit ?? true;
|
|
107
|
+
if (action === 'delete') return configValue.allowDelete ?? true;
|
|
108
|
+
if (action === 'view') return configValue.allowView ?? true;
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Deep merge helper to create full config with user overrides
|
|
114
|
+
*/
|
|
115
|
+
export function createRBACConfig(custom: Partial<{
|
|
116
|
+
apiBaseUrl: string;
|
|
117
|
+
features: Partial<{
|
|
118
|
+
masters: Partial<Record<string, FeatureToggle>>;
|
|
119
|
+
rolesAndPermissions: Partial<{
|
|
120
|
+
roleCrud: FeatureToggle;
|
|
121
|
+
permissionAssignmentMatrix: boolean;
|
|
122
|
+
permissionRegistry: boolean;
|
|
123
|
+
}>;
|
|
124
|
+
}>;
|
|
125
|
+
endpoints: Partial<typeof RBAC_CONSTANTS.endpoints>;
|
|
126
|
+
adapters: Partial<{
|
|
127
|
+
auth: Partial<RBACAdapters['auth']>;
|
|
128
|
+
roles: Partial<RBACAdapters['roles']>;
|
|
129
|
+
permissions: Partial<RBACAdapters['permissions']>;
|
|
130
|
+
masters: Partial<RBACAdapters['masters']>;
|
|
131
|
+
}>;
|
|
132
|
+
theme: Partial<RBACTheme>;
|
|
133
|
+
router: RouterAdapter;
|
|
134
|
+
pagination: Partial<PaginationConfig>;
|
|
135
|
+
permissionResolver: PermissionKeyResolverFn;
|
|
136
|
+
permissionAliases: Record<string, string>;
|
|
137
|
+
permissionKeys: Parameters<typeof configurePermissionKeys>[0];
|
|
138
|
+
constants: Parameters<typeof configureRBACConstants>[0];
|
|
139
|
+
i18n: Partial<RBACLocaleStrings>;
|
|
140
|
+
onNotification: RBACNotificationHandler;
|
|
141
|
+
getToken: () => string | null | undefined;
|
|
142
|
+
getTenantId: () => string | number | null | undefined;
|
|
143
|
+
getCustomHeaders: () => Record<string, string>;
|
|
144
|
+
refreshToken: () => Promise<string | null | undefined>;
|
|
145
|
+
onUnauthorized: () => void;
|
|
146
|
+
}> = {}): RBACConfig {
|
|
147
|
+
if (custom.constants) {
|
|
148
|
+
configureRBACConstants(custom.constants);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (custom.permissionKeys) {
|
|
152
|
+
configurePermissionKeys(custom.permissionKeys);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (custom.i18n) {
|
|
156
|
+
configureRBACConstants({ i18n: custom.i18n });
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const features: RBACFeaturesConfig = {
|
|
160
|
+
masters: {
|
|
161
|
+
...defaultRBACConfig.features.masters,
|
|
162
|
+
...(custom.features?.masters || {}),
|
|
163
|
+
},
|
|
164
|
+
rolesAndPermissions: {
|
|
165
|
+
...defaultRBACConfig.features.rolesAndPermissions,
|
|
166
|
+
...(custom.features?.rolesAndPermissions || {}),
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const endpoints = {
|
|
171
|
+
...defaultRBACConfig.endpoints,
|
|
172
|
+
...(custom.endpoints || {}),
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const adapters: RBACAdapters = {
|
|
176
|
+
auth: { ...defaultRBACConfig.adapters.auth, ...(custom.adapters?.auth || {}) },
|
|
177
|
+
roles: { ...defaultRBACConfig.adapters.roles, ...(custom.adapters?.roles || {}) },
|
|
178
|
+
permissions: { ...defaultRBACConfig.adapters.permissions, ...(custom.adapters?.permissions || {}) },
|
|
179
|
+
masters: { ...defaultRBACConfig.adapters.masters, ...(custom.adapters?.masters || {}) },
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const theme = mergeThemes(defaultRBACTheme, custom.theme);
|
|
183
|
+
const pagination = { ...defaultPaginationConfig, ...(custom.pagination || {}) };
|
|
184
|
+
|
|
185
|
+
return {
|
|
186
|
+
apiBaseUrl: custom.apiBaseUrl || defaultRBACConfig.apiBaseUrl,
|
|
187
|
+
features,
|
|
188
|
+
endpoints,
|
|
189
|
+
adapters,
|
|
190
|
+
theme,
|
|
191
|
+
router: custom.router || defaultRBACConfig.router,
|
|
192
|
+
pagination,
|
|
193
|
+
permissionResolver: custom.permissionResolver || defaultRBACConfig.permissionResolver,
|
|
194
|
+
permissionAliases: {
|
|
195
|
+
...defaultRBACConfig.permissionAliases,
|
|
196
|
+
...(custom.permissionAliases || {}),
|
|
197
|
+
},
|
|
198
|
+
i18n: custom.i18n,
|
|
199
|
+
onNotification: custom.onNotification,
|
|
200
|
+
getToken: custom.getToken || defaultRBACConfig.getToken,
|
|
201
|
+
getTenantId: custom.getTenantId || defaultRBACConfig.getTenantId,
|
|
202
|
+
getCustomHeaders: custom.getCustomHeaders || defaultRBACConfig.getCustomHeaders,
|
|
203
|
+
refreshToken: custom.refreshToken || defaultRBACConfig.refreshToken,
|
|
204
|
+
onUnauthorized: custom.onUnauthorized || defaultRBACConfig.onUnauthorized,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Action keys, IDs, and human-readable labels.
|
|
3
|
+
* Used for the Permission Matrix and Permission Gates.
|
|
4
|
+
*/
|
|
5
|
+
export const DEFAULT_ACTION_KEYS = {
|
|
6
|
+
VIEW: 'view',
|
|
7
|
+
ADD: 'add',
|
|
8
|
+
EDIT: 'edit',
|
|
9
|
+
DELETE: 'delete',
|
|
10
|
+
ASSIGN: 'assign',
|
|
11
|
+
} as const;
|
|
12
|
+
|
|
13
|
+
export const DEFAULT_ACTION_CONFIGS = [
|
|
14
|
+
{ actionId: 1, action_key: DEFAULT_ACTION_KEYS.VIEW, action_name: 'View', sort_order: 1 },
|
|
15
|
+
{ actionId: 2, action_key: DEFAULT_ACTION_KEYS.ADD, action_name: 'Add (Create)', sort_order: 2 },
|
|
16
|
+
{ actionId: 3, action_key: DEFAULT_ACTION_KEYS.EDIT, action_name: 'Edit', sort_order: 3 },
|
|
17
|
+
{ actionId: 4, action_key: DEFAULT_ACTION_KEYS.DELETE, action_name: 'Delete', sort_order: 4 },
|
|
18
|
+
{ actionId: 5, action_key: DEFAULT_ACTION_KEYS.ASSIGN, action_name: 'Assign', sort_order: 5 },
|
|
19
|
+
] as const;
|
|
20
|
+
|
|
21
|
+
export type ActionKey = (typeof DEFAULT_ACTION_KEYS)[keyof typeof DEFAULT_ACTION_KEYS] | string;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global API response and pagination field keys.
|
|
3
|
+
* If your backend wraps responses in different keys (e.g. `items` instead of `list`),
|
|
4
|
+
* change it here or pass overrides via RBACConfig.
|
|
5
|
+
*/
|
|
6
|
+
export const DEFAULT_API_RESPONSE_KEYS = {
|
|
7
|
+
SUCCESS: 'success',
|
|
8
|
+
STATUS_CODE: 'statusCode',
|
|
9
|
+
MESSAGE: 'message',
|
|
10
|
+
DATA: 'data',
|
|
11
|
+
ERROR: 'error',
|
|
12
|
+
ERRORS: 'errors',
|
|
13
|
+
} as const;
|
|
14
|
+
|
|
15
|
+
export const DEFAULT_PAGINATION_KEYS = {
|
|
16
|
+
PAGE: 'page',
|
|
17
|
+
LIMIT: 'limit',
|
|
18
|
+
TOTAL: 'total',
|
|
19
|
+
TOTAL_PAGES: 'totalPages',
|
|
20
|
+
HAS_NEXT: 'hasNext',
|
|
21
|
+
ITEMS: 'list', // container API returns `list` or direct array
|
|
22
|
+
} as const;
|
|
23
|
+
|
|
24
|
+
export type ApiResponseKeys = typeof DEFAULT_API_RESPONSE_KEYS;
|
|
25
|
+
export type PaginationKeys = typeof DEFAULT_PAGINATION_KEYS;
|