@xeplr/ui-account 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/LICENSE +21 -0
- package/package.json +17 -0
- package/src/AccessContext.jsx +125 -0
- package/src/AccessGuard.jsx +35 -0
- package/src/ProtectedRoute.jsx +55 -0
- package/src/ThemeContext.jsx +84 -0
- package/src/adminApi.js +68 -0
- package/src/api.js +196 -0
- package/src/designs/AccessMatrixSample.jsx +199 -0
- package/src/designs/ActivateSample.jsx +38 -0
- package/src/designs/ChangePasswordSample.jsx +31 -0
- package/src/designs/ForgotPasswordSample.jsx +22 -0
- package/src/designs/LoginSample.jsx +26 -0
- package/src/designs/MasterSettingsSample.jsx +206 -0
- package/src/designs/NotActivatedSample.jsx +17 -0
- package/src/designs/ProfileSample.jsx +36 -0
- package/src/designs/RegisterSample.jsx +34 -0
- package/src/designs/ResetPasswordSample.jsx +42 -0
- package/src/designs/TenantPickerSample.jsx +39 -0
- package/src/designs/TenantSample.jsx +182 -0
- package/src/designs/UserRolesMatrixSample.jsx +77 -0
- package/src/designs/admin.css +525 -0
- package/src/designs/auth.css +160 -0
- package/src/designs/index.js +13 -0
- package/src/designs/theme.css +231 -0
- package/src/index.js +41 -0
- package/src/masterApi.js +28 -0
- package/src/pages.jsx +92 -0
- package/src/token.js +38 -0
- package/src/useAccessMatrixController.js +244 -0
- package/src/useActivateController.js +37 -0
- package/src/useChangePasswordController.js +48 -0
- package/src/useForgotPasswordController.js +32 -0
- package/src/useLoginController.js +53 -0
- package/src/useMasterSettingsController.js +199 -0
- package/src/useProfileController.js +62 -0
- package/src/useRegisterController.js +41 -0
- package/src/useResetPasswordController.js +36 -0
- package/src/useTenantController.js +146 -0
- package/src/useTenantPickerController.js +97 -0
- package/src/useUserRolesController.js +81 -0
- package/src/validateDesign.js +101 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* xeplr Theme System
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* 1. Wrap your app in <ThemeProvider> (or just add a class to a container)
|
|
6
|
+
* 2. Apply class: <div class="xeplr-theme-dark"> ... </div>
|
|
7
|
+
* 3. All xeplr-ui-* components inside will pick up the theme
|
|
8
|
+
*
|
|
9
|
+
* Extending:
|
|
10
|
+
* Define your own theme by creating a class with the same variables:
|
|
11
|
+
*
|
|
12
|
+
* .xeplr-theme-corporate {
|
|
13
|
+
* --xeplr-bg-primary: #f8f9fa;
|
|
14
|
+
* --xeplr-bg-secondary: #ffffff;
|
|
15
|
+
* ... etc
|
|
16
|
+
* }
|
|
17
|
+
*
|
|
18
|
+
* Then use <ThemeProvider theme="corporate"> or class="xeplr-theme-corporate"
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/* ─── Dark ─── */
|
|
22
|
+
.xeplr-theme-dark {
|
|
23
|
+
/* Backgrounds */
|
|
24
|
+
--xeplr-bg-primary: #1a1a1a;
|
|
25
|
+
--xeplr-bg-secondary: #222222;
|
|
26
|
+
--xeplr-bg-tertiary: #2a2a2a;
|
|
27
|
+
--xeplr-bg-hover: #2a2a2a;
|
|
28
|
+
--xeplr-bg-active: #1a1a2a;
|
|
29
|
+
--xeplr-bg-overlay: rgba(0, 0, 0, 0.5);
|
|
30
|
+
|
|
31
|
+
/* Text */
|
|
32
|
+
--xeplr-text-primary: #e0e0e0;
|
|
33
|
+
--xeplr-text-secondary: #aaaaaa;
|
|
34
|
+
--xeplr-text-muted: #777777;
|
|
35
|
+
--xeplr-text-disabled: #555555;
|
|
36
|
+
|
|
37
|
+
/* Borders */
|
|
38
|
+
--xeplr-border-primary: #333333;
|
|
39
|
+
--xeplr-border-secondary: #2a2a2a;
|
|
40
|
+
--xeplr-border-focus: #646cff;
|
|
41
|
+
|
|
42
|
+
/* Accent */
|
|
43
|
+
--xeplr-accent: #646cff;
|
|
44
|
+
--xeplr-accent-hover: #535bf2;
|
|
45
|
+
--xeplr-accent-text: #ffffff;
|
|
46
|
+
--xeplr-accent-muted: #4a4a8a;
|
|
47
|
+
|
|
48
|
+
/* Semantic */
|
|
49
|
+
--xeplr-success: #6bcc6b;
|
|
50
|
+
--xeplr-success-bg: #1a2a1a;
|
|
51
|
+
--xeplr-success-border: #4aaf4a;
|
|
52
|
+
--xeplr-danger: #ff6b6b;
|
|
53
|
+
--xeplr-danger-bg: #3d1111;
|
|
54
|
+
--xeplr-danger-border: #5a1a1a;
|
|
55
|
+
--xeplr-warning: #ffaa33;
|
|
56
|
+
--xeplr-warning-bg: #2a2200;
|
|
57
|
+
--xeplr-info: #6db3ff;
|
|
58
|
+
--xeplr-info-bg: #1a2233;
|
|
59
|
+
--xeplr-info-border: #4a9eff;
|
|
60
|
+
|
|
61
|
+
/* Inputs */
|
|
62
|
+
--xeplr-input-bg: #2a2a2a;
|
|
63
|
+
--xeplr-input-border: #444444;
|
|
64
|
+
--xeplr-input-text: #e0e0e0;
|
|
65
|
+
--xeplr-input-placeholder: #666666;
|
|
66
|
+
|
|
67
|
+
/* Tags / Badges */
|
|
68
|
+
--xeplr-tag-bg: #2a2a3a;
|
|
69
|
+
--xeplr-tag-border: #3a3a5a;
|
|
70
|
+
--xeplr-tag-text: #9999cc;
|
|
71
|
+
--xeplr-badge-bg: #2a2a2a;
|
|
72
|
+
--xeplr-badge-text: #666666;
|
|
73
|
+
|
|
74
|
+
/* Shadows */
|
|
75
|
+
--xeplr-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);
|
|
76
|
+
--xeplr-shadow-md: 0 4px 12px rgba(0, 0, 0, 0.4);
|
|
77
|
+
--xeplr-shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.5);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/* ─── Light ─── */
|
|
81
|
+
.xeplr-theme-light {
|
|
82
|
+
--xeplr-bg-primary: #fafafa;
|
|
83
|
+
--xeplr-bg-secondary: #f5f5f5;
|
|
84
|
+
--xeplr-bg-tertiary: #ffffff;
|
|
85
|
+
--xeplr-bg-hover: #f0f0f0;
|
|
86
|
+
--xeplr-bg-active: #f0f0ff;
|
|
87
|
+
--xeplr-bg-overlay: rgba(0, 0, 0, 0.15);
|
|
88
|
+
|
|
89
|
+
--xeplr-text-primary: #333333;
|
|
90
|
+
--xeplr-text-secondary: #666666;
|
|
91
|
+
--xeplr-text-muted: #999999;
|
|
92
|
+
--xeplr-text-disabled: #cccccc;
|
|
93
|
+
|
|
94
|
+
--xeplr-border-primary: #dddddd;
|
|
95
|
+
--xeplr-border-secondary: #eeeeee;
|
|
96
|
+
--xeplr-border-focus: #646cff;
|
|
97
|
+
|
|
98
|
+
--xeplr-accent: #646cff;
|
|
99
|
+
--xeplr-accent-hover: #535bf2;
|
|
100
|
+
--xeplr-accent-text: #ffffff;
|
|
101
|
+
--xeplr-accent-muted: #b0b0e0;
|
|
102
|
+
|
|
103
|
+
--xeplr-success: #2d7d2d;
|
|
104
|
+
--xeplr-success-bg: #e8f5e8;
|
|
105
|
+
--xeplr-success-border: #3d9e3d;
|
|
106
|
+
--xeplr-danger: #cc0000;
|
|
107
|
+
--xeplr-danger-bg: #ffe0e0;
|
|
108
|
+
--xeplr-danger-border: #f5c6c6;
|
|
109
|
+
--xeplr-warning: #e67e00;
|
|
110
|
+
--xeplr-warning-bg: #fff3e0;
|
|
111
|
+
--xeplr-info: #2a6cb5;
|
|
112
|
+
--xeplr-info-bg: #e8f0ff;
|
|
113
|
+
--xeplr-info-border: #3b8bdb;
|
|
114
|
+
|
|
115
|
+
--xeplr-input-bg: #ffffff;
|
|
116
|
+
--xeplr-input-border: #cccccc;
|
|
117
|
+
--xeplr-input-text: #333333;
|
|
118
|
+
--xeplr-input-placeholder: #aaaaaa;
|
|
119
|
+
|
|
120
|
+
--xeplr-tag-bg: #eeeeff;
|
|
121
|
+
--xeplr-tag-border: #ddddee;
|
|
122
|
+
--xeplr-tag-text: #5555cc;
|
|
123
|
+
--xeplr-badge-bg: #eeeeee;
|
|
124
|
+
--xeplr-badge-text: #888888;
|
|
125
|
+
|
|
126
|
+
--xeplr-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.06);
|
|
127
|
+
--xeplr-shadow-md: 0 4px 12px rgba(0, 0, 0, 0.08);
|
|
128
|
+
--xeplr-shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.12);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/* ─── Medium (balanced gray) ─── */
|
|
132
|
+
.xeplr-theme-medium {
|
|
133
|
+
--xeplr-bg-primary: #2c2c2e;
|
|
134
|
+
--xeplr-bg-secondary: #3a3a3c;
|
|
135
|
+
--xeplr-bg-tertiary: #48484a;
|
|
136
|
+
--xeplr-bg-hover: #3a3a3c;
|
|
137
|
+
--xeplr-bg-active: #2c2c3e;
|
|
138
|
+
--xeplr-bg-overlay: rgba(0, 0, 0, 0.35);
|
|
139
|
+
|
|
140
|
+
--xeplr-text-primary: #e5e5e7;
|
|
141
|
+
--xeplr-text-secondary: #b0b0b2;
|
|
142
|
+
--xeplr-text-muted: #8e8e93;
|
|
143
|
+
--xeplr-text-disabled: #636366;
|
|
144
|
+
|
|
145
|
+
--xeplr-border-primary: #48484a;
|
|
146
|
+
--xeplr-border-secondary: #3a3a3c;
|
|
147
|
+
--xeplr-border-focus: #7b7fff;
|
|
148
|
+
|
|
149
|
+
--xeplr-accent: #7b7fff;
|
|
150
|
+
--xeplr-accent-hover: #6a6ef0;
|
|
151
|
+
--xeplr-accent-text: #ffffff;
|
|
152
|
+
--xeplr-accent-muted: #5a5a9a;
|
|
153
|
+
|
|
154
|
+
--xeplr-success: #7cd87c;
|
|
155
|
+
--xeplr-success-bg: #253025;
|
|
156
|
+
--xeplr-success-border: #5aaa5a;
|
|
157
|
+
--xeplr-danger: #ff7070;
|
|
158
|
+
--xeplr-danger-bg: #402020;
|
|
159
|
+
--xeplr-danger-border: #6a2a2a;
|
|
160
|
+
--xeplr-warning: #ffbb55;
|
|
161
|
+
--xeplr-warning-bg: #352a10;
|
|
162
|
+
--xeplr-info: #7abcff;
|
|
163
|
+
--xeplr-info-bg: #202a3a;
|
|
164
|
+
--xeplr-info-border: #5aaaee;
|
|
165
|
+
|
|
166
|
+
--xeplr-input-bg: #3a3a3c;
|
|
167
|
+
--xeplr-input-border: #555558;
|
|
168
|
+
--xeplr-input-text: #e5e5e7;
|
|
169
|
+
--xeplr-input-placeholder: #777777;
|
|
170
|
+
|
|
171
|
+
--xeplr-tag-bg: #3a3a4a;
|
|
172
|
+
--xeplr-tag-border: #4a4a5a;
|
|
173
|
+
--xeplr-tag-text: #aaaadd;
|
|
174
|
+
--xeplr-badge-bg: #3a3a3c;
|
|
175
|
+
--xeplr-badge-text: #8e8e93;
|
|
176
|
+
|
|
177
|
+
--xeplr-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.2);
|
|
178
|
+
--xeplr-shadow-md: 0 4px 12px rgba(0, 0, 0, 0.3);
|
|
179
|
+
--xeplr-shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.35);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/* ─── Bright (vibrant, energetic) ─── */
|
|
183
|
+
.xeplr-theme-bright {
|
|
184
|
+
--xeplr-bg-primary: #ffffff;
|
|
185
|
+
--xeplr-bg-secondary: #f0f2ff;
|
|
186
|
+
--xeplr-bg-tertiary: #ffffff;
|
|
187
|
+
--xeplr-bg-hover: #e8ebff;
|
|
188
|
+
--xeplr-bg-active: #e0e4ff;
|
|
189
|
+
--xeplr-bg-overlay: rgba(50, 50, 100, 0.15);
|
|
190
|
+
|
|
191
|
+
--xeplr-text-primary: #1a1a2e;
|
|
192
|
+
--xeplr-text-secondary: #444466;
|
|
193
|
+
--xeplr-text-muted: #888899;
|
|
194
|
+
--xeplr-text-disabled: #bbbbcc;
|
|
195
|
+
|
|
196
|
+
--xeplr-border-primary: #d0d4ee;
|
|
197
|
+
--xeplr-border-secondary: #e0e4f4;
|
|
198
|
+
--xeplr-border-focus: #5b5fff;
|
|
199
|
+
|
|
200
|
+
--xeplr-accent: #5b5fff;
|
|
201
|
+
--xeplr-accent-hover: #4a4ef0;
|
|
202
|
+
--xeplr-accent-text: #ffffff;
|
|
203
|
+
--xeplr-accent-muted: #c0c2f0;
|
|
204
|
+
|
|
205
|
+
--xeplr-success: #1aaa55;
|
|
206
|
+
--xeplr-success-bg: #e0fae8;
|
|
207
|
+
--xeplr-success-border: #2ecc71;
|
|
208
|
+
--xeplr-danger: #e53e3e;
|
|
209
|
+
--xeplr-danger-bg: #ffe8e8;
|
|
210
|
+
--xeplr-danger-border: #fc8181;
|
|
211
|
+
--xeplr-warning: #dd8800;
|
|
212
|
+
--xeplr-warning-bg: #fff6e0;
|
|
213
|
+
--xeplr-info: #3182ce;
|
|
214
|
+
--xeplr-info-bg: #e0efff;
|
|
215
|
+
--xeplr-info-border: #63b3ed;
|
|
216
|
+
|
|
217
|
+
--xeplr-input-bg: #ffffff;
|
|
218
|
+
--xeplr-input-border: #c0c4dd;
|
|
219
|
+
--xeplr-input-text: #1a1a2e;
|
|
220
|
+
--xeplr-input-placeholder: #999aab;
|
|
221
|
+
|
|
222
|
+
--xeplr-tag-bg: #ebebff;
|
|
223
|
+
--xeplr-tag-border: #d0d0ee;
|
|
224
|
+
--xeplr-tag-text: #5555aa;
|
|
225
|
+
--xeplr-badge-bg: #e8eaff;
|
|
226
|
+
--xeplr-badge-text: #6666aa;
|
|
227
|
+
|
|
228
|
+
--xeplr-shadow-sm: 0 1px 3px rgba(90, 90, 180, 0.08);
|
|
229
|
+
--xeplr-shadow-md: 0 4px 12px rgba(90, 90, 180, 0.1);
|
|
230
|
+
--xeplr-shadow-lg: 0 8px 24px rgba(90, 90, 180, 0.15);
|
|
231
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// API client
|
|
2
|
+
export { configure, registerUser, loginUser, activateAccount, forgotPassword, resetPassword, changePassword, getProfile, updateProfile, logoutUser, getMyTenants, authFetch } from './api.js';
|
|
3
|
+
|
|
4
|
+
// Token helpers
|
|
5
|
+
export { getToken, setToken, getRefreshToken, setRefreshToken, getUser, setUser, clearAuth, isAuthenticated } from './token.js';
|
|
6
|
+
|
|
7
|
+
// Controller hooks
|
|
8
|
+
export { useLoginController } from './useLoginController.js';
|
|
9
|
+
export { useRegisterController } from './useRegisterController.js';
|
|
10
|
+
export { useForgotPasswordController } from './useForgotPasswordController.js';
|
|
11
|
+
export { useResetPasswordController } from './useResetPasswordController.js';
|
|
12
|
+
export { useActivateController } from './useActivateController.js';
|
|
13
|
+
export { useChangePasswordController } from './useChangePasswordController.js';
|
|
14
|
+
export { useProfileController } from './useProfileController.js';
|
|
15
|
+
export { useUserRolesController } from './useUserRolesController.js';
|
|
16
|
+
export { useAccessMatrixController } from './useAccessMatrixController.js';
|
|
17
|
+
export { useMasterSettingsController } from './useMasterSettingsController.js';
|
|
18
|
+
export { useTenantController } from './useTenantController.js';
|
|
19
|
+
export { useTenantPickerController, getActiveTenant, setActiveTenant, clearActiveTenant, hasMultipleTenants } from './useTenantPickerController.js';
|
|
20
|
+
|
|
21
|
+
// Theme
|
|
22
|
+
export { ThemeProvider, useTheme, useThemeStrict, BUILT_IN_THEMES, DEFAULT_THEME } from './ThemeContext.jsx';
|
|
23
|
+
|
|
24
|
+
// Access control
|
|
25
|
+
export { AccessProvider, useAccess, useAccessStrict } from './AccessContext.jsx';
|
|
26
|
+
export { ProtectedRoute } from './ProtectedRoute.jsx';
|
|
27
|
+
export { AccessGuard } from './AccessGuard.jsx';
|
|
28
|
+
|
|
29
|
+
// Admin API (model layer for RBAC management)
|
|
30
|
+
export { getUsers, getRoles, getAccessItems, toggleUserRole, toggleAccessRole, toggleModuleRole } from './adminApi.js';
|
|
31
|
+
export { getMasterItems, saveMasterItem, deleteMasterItem, MASTER_TYPES } from './masterApi.js';
|
|
32
|
+
export { getTenants, saveTenant, deleteTenant, assignUserTenant } from './adminApi.js';
|
|
33
|
+
|
|
34
|
+
// Design validation (use when building custom designs)
|
|
35
|
+
export { useDesignValidator, LOGIN_RULES, REGISTER_RULES, FORGOT_PASSWORD_RULES, RESET_PASSWORD_RULES, CHANGE_PASSWORD_RULES, PROFILE_RULES, USER_ROLES_MATRIX_RULES, ACCESS_MATRIX_RULES, MASTER_SETTINGS_RULES } from './validateDesign.js';
|
|
36
|
+
|
|
37
|
+
// Sample designs (use as reference or starting point)
|
|
38
|
+
export { LoginSample, RegisterSample, ForgotPasswordSample, ResetPasswordSample, ActivateSample, NotActivatedSample, ChangePasswordSample, ProfileSample, UserRolesMatrixSample, AccessMatrixSample, MasterSettingsSample, TenantSample } from './designs/index.js';
|
|
39
|
+
|
|
40
|
+
// Ready-made pages (controller + sample design wired together)
|
|
41
|
+
export { LoginPage, RegisterPage, ForgotPasswordPage, ResetPasswordPage, ActivatePage, NotActivatedPage, ChangePasswordPage, ProfilePage, UserRolesPage, AccessMatrixPage, MasterSettingsPage, TenantPage, TenantPickerPage } from './pages.jsx';
|
package/src/masterApi.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { authFetch } from './api.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Master Settings API — model layer for managing auth master data.
|
|
5
|
+
* Pure logic, no React dependency.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
var TYPES = ['roles', 'apis', 'pages', 'elements', 'menus'];
|
|
9
|
+
|
|
10
|
+
export function getMasterItems(type) {
|
|
11
|
+
return authFetch('/auth/api/admin/master/' + type);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function saveMasterItem(type, data) {
|
|
15
|
+
return authFetch('/auth/api/admin/master/' + type, {
|
|
16
|
+
method: 'POST',
|
|
17
|
+
body: JSON.stringify(data),
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function deleteMasterItem(type, id) {
|
|
22
|
+
return authFetch('/auth/api/admin/master/' + type + '/delete', {
|
|
23
|
+
method: 'POST',
|
|
24
|
+
body: JSON.stringify({ id }),
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export { TYPES as MASTER_TYPES };
|
package/src/pages.jsx
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { useLoginController } from './useLoginController.js';
|
|
2
|
+
import { useRegisterController } from './useRegisterController.js';
|
|
3
|
+
import { useForgotPasswordController } from './useForgotPasswordController.js';
|
|
4
|
+
import { useResetPasswordController } from './useResetPasswordController.js';
|
|
5
|
+
import { useActivateController } from './useActivateController.js';
|
|
6
|
+
import { useChangePasswordController } from './useChangePasswordController.js';
|
|
7
|
+
import { useProfileController } from './useProfileController.js';
|
|
8
|
+
import { useUserRolesController } from './useUserRolesController.js';
|
|
9
|
+
import { useAccessMatrixController } from './useAccessMatrixController.js';
|
|
10
|
+
import { useMasterSettingsController } from './useMasterSettingsController.js';
|
|
11
|
+
import { useTenantController } from './useTenantController.js';
|
|
12
|
+
import { useTenantPickerController } from './useTenantPickerController.js';
|
|
13
|
+
import { LoginSample, RegisterSample, ForgotPasswordSample, ResetPasswordSample, ActivateSample, NotActivatedSample, ChangePasswordSample, ProfileSample, UserRolesMatrixSample, AccessMatrixSample, MasterSettingsSample, TenantSample, TenantPickerSample } from './designs/index.js';
|
|
14
|
+
import { useDesignValidator, LOGIN_RULES, REGISTER_RULES, FORGOT_PASSWORD_RULES, RESET_PASSWORD_RULES, CHANGE_PASSWORD_RULES, PROFILE_RULES, USER_ROLES_MATRIX_RULES, ACCESS_MATRIX_RULES, MASTER_SETTINGS_RULES } from './validateDesign.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Ready-made pages using sample designs.
|
|
18
|
+
* Use these for quick setup, or build your own with the controllers + your own designs.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
export function LoginPage(props) {
|
|
22
|
+
var controller = useLoginController(props);
|
|
23
|
+
var ref = useDesignValidator('LoginPage', LOGIN_RULES);
|
|
24
|
+
return <div ref={ref}><LoginSample {...controller} /></div>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function RegisterPage(props) {
|
|
28
|
+
var controller = useRegisterController(props);
|
|
29
|
+
var ref = useDesignValidator('RegisterPage', REGISTER_RULES);
|
|
30
|
+
return <div ref={ref}><RegisterSample {...controller} /></div>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function ForgotPasswordPage() {
|
|
34
|
+
var controller = useForgotPasswordController();
|
|
35
|
+
var ref = useDesignValidator('ForgotPasswordPage', FORGOT_PASSWORD_RULES);
|
|
36
|
+
return <div ref={ref}><ForgotPasswordSample {...controller} /></div>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function ResetPasswordPage() {
|
|
40
|
+
var controller = useResetPasswordController();
|
|
41
|
+
var ref = useDesignValidator('ResetPasswordPage', RESET_PASSWORD_RULES);
|
|
42
|
+
return <div ref={ref}><ResetPasswordSample {...controller} /></div>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function ActivatePage() {
|
|
46
|
+
var controller = useActivateController();
|
|
47
|
+
return <ActivateSample {...controller} />;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function NotActivatedPage() {
|
|
51
|
+
return <NotActivatedSample />;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function ChangePasswordPage(props) {
|
|
55
|
+
var controller = useChangePasswordController(props);
|
|
56
|
+
var ref = useDesignValidator('ChangePasswordPage', CHANGE_PASSWORD_RULES);
|
|
57
|
+
return <div ref={ref}><ChangePasswordSample {...controller} /></div>;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function ProfilePage(props) {
|
|
61
|
+
var controller = useProfileController(props);
|
|
62
|
+
var ref = useDesignValidator('ProfilePage', PROFILE_RULES);
|
|
63
|
+
return <div ref={ref}><ProfileSample {...controller} /></div>;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function UserRolesPage(props) {
|
|
67
|
+
var controller = useUserRolesController(props);
|
|
68
|
+
var ref = useDesignValidator('UserRolesPage', USER_ROLES_MATRIX_RULES);
|
|
69
|
+
return <div ref={ref}><UserRolesMatrixSample {...controller} /></div>;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function AccessMatrixPage(props) {
|
|
73
|
+
var controller = useAccessMatrixController(props);
|
|
74
|
+
var ref = useDesignValidator('AccessMatrixPage', ACCESS_MATRIX_RULES);
|
|
75
|
+
return <div ref={ref}><AccessMatrixSample {...controller} /></div>;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function MasterSettingsPage(props) {
|
|
79
|
+
var controller = useMasterSettingsController(props);
|
|
80
|
+
var ref = useDesignValidator('MasterSettingsPage', MASTER_SETTINGS_RULES);
|
|
81
|
+
return <div ref={ref}><MasterSettingsSample {...controller} /></div>;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function TenantPage(props) {
|
|
85
|
+
var controller = useTenantController(props);
|
|
86
|
+
return <TenantSample {...controller} />;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function TenantPickerPage(props) {
|
|
90
|
+
var controller = useTenantPickerController(props);
|
|
91
|
+
return <TenantPickerSample {...controller} />;
|
|
92
|
+
}
|
package/src/token.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const TOKEN_KEY = 'accessToken';
|
|
2
|
+
const REFRESH_TOKEN_KEY = 'refreshToken';
|
|
3
|
+
const USER_KEY = 'user';
|
|
4
|
+
|
|
5
|
+
export function getToken() {
|
|
6
|
+
return localStorage.getItem(TOKEN_KEY);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function setToken(token) {
|
|
10
|
+
localStorage.setItem(TOKEN_KEY, token);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function getRefreshToken() {
|
|
14
|
+
return localStorage.getItem(REFRESH_TOKEN_KEY);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function setRefreshToken(token) {
|
|
18
|
+
localStorage.setItem(REFRESH_TOKEN_KEY, token);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function getUser() {
|
|
22
|
+
const raw = localStorage.getItem(USER_KEY);
|
|
23
|
+
return raw ? JSON.parse(raw) : null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function setUser(user) {
|
|
27
|
+
localStorage.setItem(USER_KEY, JSON.stringify(user));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function clearAuth() {
|
|
31
|
+
localStorage.removeItem(TOKEN_KEY);
|
|
32
|
+
localStorage.removeItem(REFRESH_TOKEN_KEY);
|
|
33
|
+
localStorage.removeItem(USER_KEY);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function isAuthenticated() {
|
|
37
|
+
return !!getToken();
|
|
38
|
+
}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { useState, useEffect, useMemo, useCallback } from 'react';
|
|
2
|
+
import { getRoles, getAccessItems, toggleModuleRole, toggleAccessRole } from './adminApi.js';
|
|
3
|
+
|
|
4
|
+
var GROUP_FIELDS = {
|
|
5
|
+
apis: 'apiGroup',
|
|
6
|
+
pages: 'uiPagesGroup',
|
|
7
|
+
elements: 'uiElementsGroup',
|
|
8
|
+
menus: 'menuGroup',
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
var TYPES = ['apis', 'pages', 'elements', 'menus'];
|
|
12
|
+
|
|
13
|
+
var ACTION_ORDER = { view: 0, edit: 1, delete: 2 };
|
|
14
|
+
|
|
15
|
+
function parseModuleGroup(groupValue) {
|
|
16
|
+
if (!groupValue) return null;
|
|
17
|
+
var parts = groupValue.split(':');
|
|
18
|
+
if (parts.length !== 2 || !parts[0] || !parts[1]) return null;
|
|
19
|
+
return { module: parts[0], action: parts[1] };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function useAccessMatrixController() {
|
|
23
|
+
var [roles, setRoles] = useState([]);
|
|
24
|
+
var [rawItems, setRawItems] = useState({ apis: [], pages: [], elements: [], menus: [] });
|
|
25
|
+
var [activeView, setActiveView] = useState('modules');
|
|
26
|
+
var [search, setSearch] = useState('');
|
|
27
|
+
var [uncatSubTab, setUncatSubTab] = useState('apis');
|
|
28
|
+
var [loading, setLoading] = useState(true);
|
|
29
|
+
var [error, setError] = useState('');
|
|
30
|
+
var [saving, setSaving] = useState({});
|
|
31
|
+
|
|
32
|
+
useEffect(function() {
|
|
33
|
+
loadData();
|
|
34
|
+
}, []);
|
|
35
|
+
|
|
36
|
+
async function loadData() {
|
|
37
|
+
setLoading(true);
|
|
38
|
+
setError('');
|
|
39
|
+
try {
|
|
40
|
+
var [rolesData, itemsData] = await Promise.all([getRoles(), getAccessItems()]);
|
|
41
|
+
setRoles(rolesData.filter(function(r) { return r.name !== 'Super Admin'; }));
|
|
42
|
+
setRawItems(itemsData);
|
|
43
|
+
} catch (err) {
|
|
44
|
+
setError(err.message);
|
|
45
|
+
} finally {
|
|
46
|
+
setLoading(false);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ─── Modules view: aggregate items into module > action > roles ───
|
|
51
|
+
var modules = useMemo(function() {
|
|
52
|
+
var moduleMap = {};
|
|
53
|
+
|
|
54
|
+
TYPES.forEach(function(type) {
|
|
55
|
+
var items = rawItems[type] || [];
|
|
56
|
+
var groupField = GROUP_FIELDS[type];
|
|
57
|
+
|
|
58
|
+
items.forEach(function(item) {
|
|
59
|
+
var parsed = parseModuleGroup(item[groupField]);
|
|
60
|
+
if (!parsed) return;
|
|
61
|
+
|
|
62
|
+
var key = parsed.module;
|
|
63
|
+
if (!moduleMap[key]) moduleMap[key] = {};
|
|
64
|
+
if (!moduleMap[key][parsed.action]) {
|
|
65
|
+
moduleMap[key][parsed.action] = { items: [], roleIds: {} };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
var entry = moduleMap[key][parsed.action];
|
|
69
|
+
entry.items.push({ type: type, item: item });
|
|
70
|
+
|
|
71
|
+
// Track which roles have ALL items in this group assigned
|
|
72
|
+
var itemRoleIds = (item.roles || []).map(function(r) { return r.id; });
|
|
73
|
+
roles.forEach(function(role) {
|
|
74
|
+
if (entry.roleIds[role.id] === undefined) entry.roleIds[role.id] = { total: 0, assigned: 0 };
|
|
75
|
+
entry.roleIds[role.id].total++;
|
|
76
|
+
if (itemRoleIds.indexOf(role.id) !== -1) entry.roleIds[role.id].assigned++;
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// Convert to sorted array
|
|
82
|
+
var q = search.toLowerCase();
|
|
83
|
+
var result = Object.keys(moduleMap).sort().filter(function(name) {
|
|
84
|
+
return !q || name.toLowerCase().includes(q);
|
|
85
|
+
}).map(function(name) {
|
|
86
|
+
var actions = Object.keys(moduleMap[name]).sort(function(a, b) {
|
|
87
|
+
var oa = ACTION_ORDER[a] !== undefined ? ACTION_ORDER[a] : 99;
|
|
88
|
+
var ob = ACTION_ORDER[b] !== undefined ? ACTION_ORDER[b] : 99;
|
|
89
|
+
return oa - ob;
|
|
90
|
+
}).map(function(action) {
|
|
91
|
+
var data = moduleMap[name][action];
|
|
92
|
+
return {
|
|
93
|
+
action: action,
|
|
94
|
+
itemCount: data.items.length,
|
|
95
|
+
items: data.items,
|
|
96
|
+
getRoleState: function(roleId) {
|
|
97
|
+
var info = data.roleIds[roleId];
|
|
98
|
+
if (!info || info.total === 0) return 'none';
|
|
99
|
+
if (info.assigned === info.total) return 'all';
|
|
100
|
+
if (info.assigned > 0) return 'partial';
|
|
101
|
+
return 'none';
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
return { name: name, actions: actions };
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
return result;
|
|
110
|
+
}, [rawItems, roles, search]);
|
|
111
|
+
|
|
112
|
+
// ─── Uncategorized view: items without module:action group ───
|
|
113
|
+
var uncategorized = useMemo(function() {
|
|
114
|
+
var result = {};
|
|
115
|
+
var q = search.toLowerCase();
|
|
116
|
+
|
|
117
|
+
TYPES.forEach(function(type) {
|
|
118
|
+
var items = rawItems[type] || [];
|
|
119
|
+
var groupField = GROUP_FIELDS[type];
|
|
120
|
+
|
|
121
|
+
result[type] = items.filter(function(item) {
|
|
122
|
+
var parsed = parseModuleGroup(item[groupField]);
|
|
123
|
+
if (parsed) return false; // categorized, skip
|
|
124
|
+
var matchesSearch = !q || (item.name && item.name.toLowerCase().includes(q));
|
|
125
|
+
return matchesSearch;
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
return result;
|
|
130
|
+
}, [rawItems, search]);
|
|
131
|
+
|
|
132
|
+
var uncategorizedCount = useMemo(function() {
|
|
133
|
+
var count = 0;
|
|
134
|
+
TYPES.forEach(function(type) { count += (uncategorized[type] || []).length; });
|
|
135
|
+
return count;
|
|
136
|
+
}, [uncategorized]);
|
|
137
|
+
|
|
138
|
+
// ─── Module toggle (bulk) ───
|
|
139
|
+
var handleModuleToggle = useCallback(async function(moduleName, action, roleId, currentState) {
|
|
140
|
+
var assign = currentState !== 'all';
|
|
141
|
+
var key = 'module:' + moduleName + ':' + action + ':' + roleId;
|
|
142
|
+
setSaving(function(prev) { var next = { ...prev }; next[key] = true; return next; });
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
await toggleModuleRole({ module: moduleName, action: action, roleId: roleId, assign: assign });
|
|
146
|
+
|
|
147
|
+
// Optimistic: update local state
|
|
148
|
+
setRawItems(function(prev) {
|
|
149
|
+
var updated = {};
|
|
150
|
+
TYPES.forEach(function(type) {
|
|
151
|
+
var groupField = GROUP_FIELDS[type];
|
|
152
|
+
updated[type] = prev[type].map(function(item) {
|
|
153
|
+
var parsed = parseModuleGroup(item[groupField]);
|
|
154
|
+
if (!parsed || parsed.module !== moduleName || parsed.action !== action) return item;
|
|
155
|
+
|
|
156
|
+
var newRoles;
|
|
157
|
+
if (assign) {
|
|
158
|
+
var hasRole = item.roles && item.roles.some(function(r) { return r.id === roleId; });
|
|
159
|
+
if (hasRole) return item;
|
|
160
|
+
var role = roles.find(function(r) { return r.id === roleId; });
|
|
161
|
+
newRoles = (item.roles || []).concat(role ? [role] : []);
|
|
162
|
+
} else {
|
|
163
|
+
newRoles = (item.roles || []).filter(function(r) { return r.id !== roleId; });
|
|
164
|
+
}
|
|
165
|
+
return { ...item, roles: newRoles };
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
return updated;
|
|
169
|
+
});
|
|
170
|
+
} catch (err) {
|
|
171
|
+
setError(err.message);
|
|
172
|
+
} finally {
|
|
173
|
+
setSaving(function(prev) { var next = { ...prev }; delete next[key]; return next; });
|
|
174
|
+
}
|
|
175
|
+
}, [roles]);
|
|
176
|
+
|
|
177
|
+
// ─── Single item toggle (for uncategorized) ───
|
|
178
|
+
var handleItemToggle = useCallback(async function(type, itemId, roleId, currentlyAssigned) {
|
|
179
|
+
var key = 'item:' + type + ':' + itemId + ':' + roleId;
|
|
180
|
+
setSaving(function(prev) { var next = { ...prev }; next[key] = true; return next; });
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
await toggleAccessRole({ type: type, itemId: itemId, roleId: roleId, assign: !currentlyAssigned });
|
|
184
|
+
|
|
185
|
+
setRawItems(function(prev) {
|
|
186
|
+
var updated = { ...prev };
|
|
187
|
+
updated[type] = prev[type].map(function(item) {
|
|
188
|
+
if (item.id !== itemId) return item;
|
|
189
|
+
var newRoles;
|
|
190
|
+
if (currentlyAssigned) {
|
|
191
|
+
newRoles = item.roles.filter(function(r) { return r.id !== roleId; });
|
|
192
|
+
} else {
|
|
193
|
+
var role = roles.find(function(r) { return r.id === roleId; });
|
|
194
|
+
newRoles = item.roles.concat(role ? [role] : []);
|
|
195
|
+
}
|
|
196
|
+
return { ...item, roles: newRoles };
|
|
197
|
+
});
|
|
198
|
+
return updated;
|
|
199
|
+
});
|
|
200
|
+
} catch (err) {
|
|
201
|
+
setError(err.message);
|
|
202
|
+
} finally {
|
|
203
|
+
setSaving(function(prev) { var next = { ...prev }; delete next[key]; return next; });
|
|
204
|
+
}
|
|
205
|
+
}, [roles]);
|
|
206
|
+
|
|
207
|
+
function isItemAssigned(item, roleId) {
|
|
208
|
+
return item.roles && item.roles.some(function(r) { return r.id === roleId; });
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function isModuleSaving(moduleName, action, roleId) {
|
|
212
|
+
return !!saving['module:' + moduleName + ':' + action + ':' + roleId];
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function isItemSaving(type, itemId, roleId) {
|
|
216
|
+
return !!saving['item:' + type + ':' + itemId + ':' + roleId];
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
var handleViewChange = useCallback(function(view) {
|
|
220
|
+
setActiveView(view);
|
|
221
|
+
setSearch('');
|
|
222
|
+
}, []);
|
|
223
|
+
|
|
224
|
+
return {
|
|
225
|
+
roles,
|
|
226
|
+
modules,
|
|
227
|
+
uncategorized,
|
|
228
|
+
uncategorizedCount,
|
|
229
|
+
uncatSubTab,
|
|
230
|
+
setUncatSubTab,
|
|
231
|
+
activeView,
|
|
232
|
+
setActiveView: handleViewChange,
|
|
233
|
+
search,
|
|
234
|
+
setSearch,
|
|
235
|
+
loading,
|
|
236
|
+
error,
|
|
237
|
+
handleModuleToggle,
|
|
238
|
+
handleItemToggle,
|
|
239
|
+
isItemAssigned,
|
|
240
|
+
isModuleSaving,
|
|
241
|
+
isItemSaving,
|
|
242
|
+
reload: loadData,
|
|
243
|
+
};
|
|
244
|
+
}
|