@xeplr/ui-account 1.0.0 → 1.0.1
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/package.json +25 -5
- package/src/activeScope.js +46 -0
- package/src/adminApi.js +0 -29
- package/src/api.js +57 -14
- package/src/authRoutes.jsx +125 -0
- package/src/designs/AccountMenu.jsx +71 -0
- package/src/designs/ActivateSample.jsx +8 -16
- package/src/designs/ChangePasswordSample.jsx +3 -3
- package/src/designs/ForgotPasswordSample.jsx +3 -3
- package/src/designs/LoginSample.jsx +3 -2
- package/src/designs/NavDrawer.jsx +121 -0
- package/src/designs/NavTopSample.jsx +34 -0
- package/src/designs/NotificationsBell.jsx +31 -0
- package/src/designs/ProfileSample.jsx +3 -3
- package/src/designs/RegisterSample.jsx +3 -3
- package/src/designs/ResetPasswordSample.jsx +7 -9
- package/src/designs/admin.css +13 -3
- package/src/designs/auth.css +6 -60
- package/src/designs/index.js +4 -2
- package/src/designs/nav.css +311 -0
- package/src/index.js +19 -8
- package/src/mt.js +31 -0
- package/src/pages.jsx +85 -38
- package/src/useActivateController.js +13 -2
- package/src/useChangePasswordController.js +8 -2
- package/src/useForgotPasswordController.js +3 -0
- package/src/useLoginController.js +2 -0
- package/src/useNavController.js +102 -0
- package/src/useProfileController.js +6 -1
- package/src/useRegisterController.js +5 -1
- package/src/useResetPasswordController.js +12 -1
- package/src/validateDesign.js +6 -0
- package/src/designs/TenantPickerSample.jsx +0 -39
- package/src/designs/TenantSample.jsx +0 -182
- package/src/useTenantController.js +0 -146
- package/src/useTenantPickerController.js +0 -97
package/src/pages.jsx
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { memo } from 'react';
|
|
1
2
|
import { useLoginController } from './useLoginController.js';
|
|
2
3
|
import { useRegisterController } from './useRegisterController.js';
|
|
3
4
|
import { useForgotPasswordController } from './useForgotPasswordController.js';
|
|
@@ -8,85 +9,131 @@ import { useProfileController } from './useProfileController.js';
|
|
|
8
9
|
import { useUserRolesController } from './useUserRolesController.js';
|
|
9
10
|
import { useAccessMatrixController } from './useAccessMatrixController.js';
|
|
10
11
|
import { useMasterSettingsController } from './useMasterSettingsController.js';
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
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';
|
|
12
|
+
import { useNavController } from './useNavController.js';
|
|
13
|
+
import { LoginSample, RegisterSample, ForgotPasswordSample, ResetPasswordSample, ActivateSample, NotActivatedSample, ChangePasswordSample, ProfileSample, UserRolesMatrixSample, AccessMatrixSample, MasterSettingsSample, NavTopSample, NavDrawer } 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, NAV_RULES } from './validateDesign.js';
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
-
* Ready-made pages
|
|
18
|
-
*
|
|
17
|
+
* Ready-made pages: controller (the logic) + sample design (the look), wired together.
|
|
18
|
+
*
|
|
19
|
+
* Every page takes an optional `design` prop — pass your OWN design component to
|
|
20
|
+
* re-skin the page while keeping the framework's controller + validation. The design
|
|
21
|
+
* receives the controller's output as props (the same contract the sample honours), e.g.
|
|
22
|
+
* LoginSample gets { email, setEmail, password, setPassword, error, loading, handleSubmit }.
|
|
23
|
+
*
|
|
24
|
+
* <LoginPage design={MyLogin} onSuccess={...} /> // my look, framework logic
|
|
25
|
+
* <LoginPage onSuccess={...} /> // framework look
|
|
26
|
+
*
|
|
27
|
+
* Any other props (onSuccess, etc.) flow through to the controller.
|
|
19
28
|
*/
|
|
20
29
|
|
|
21
|
-
export function LoginPage(props) {
|
|
30
|
+
export function LoginPage({ design, ...props }) {
|
|
22
31
|
var controller = useLoginController(props);
|
|
23
32
|
var ref = useDesignValidator('LoginPage', LOGIN_RULES);
|
|
24
|
-
|
|
33
|
+
var View = design || LoginSample;
|
|
34
|
+
return <div ref={ref}><View {...controller} /></div>;
|
|
25
35
|
}
|
|
26
36
|
|
|
27
|
-
export function RegisterPage(props) {
|
|
37
|
+
export function RegisterPage({ design, ...props }) {
|
|
28
38
|
var controller = useRegisterController(props);
|
|
29
39
|
var ref = useDesignValidator('RegisterPage', REGISTER_RULES);
|
|
30
|
-
|
|
40
|
+
var View = design || RegisterSample;
|
|
41
|
+
return <div ref={ref}><View {...controller} /></div>;
|
|
31
42
|
}
|
|
32
43
|
|
|
33
|
-
export function ForgotPasswordPage() {
|
|
34
|
-
var controller = useForgotPasswordController();
|
|
44
|
+
export function ForgotPasswordPage({ design, ...props }) {
|
|
45
|
+
var controller = useForgotPasswordController(props);
|
|
35
46
|
var ref = useDesignValidator('ForgotPasswordPage', FORGOT_PASSWORD_RULES);
|
|
36
|
-
|
|
47
|
+
var View = design || ForgotPasswordSample;
|
|
48
|
+
return <div ref={ref}><View {...controller} /></div>;
|
|
37
49
|
}
|
|
38
50
|
|
|
39
|
-
export function ResetPasswordPage() {
|
|
40
|
-
var controller = useResetPasswordController();
|
|
51
|
+
export function ResetPasswordPage({ design, ...props }) {
|
|
52
|
+
var controller = useResetPasswordController(props);
|
|
41
53
|
var ref = useDesignValidator('ResetPasswordPage', RESET_PASSWORD_RULES);
|
|
42
|
-
|
|
54
|
+
var View = design || ResetPasswordSample;
|
|
55
|
+
return <div ref={ref}><View {...controller} /></div>;
|
|
43
56
|
}
|
|
44
57
|
|
|
45
|
-
export function ActivatePage() {
|
|
46
|
-
var controller = useActivateController();
|
|
47
|
-
|
|
58
|
+
export function ActivatePage({ design, ...props }) {
|
|
59
|
+
var controller = useActivateController(props);
|
|
60
|
+
var View = design || ActivateSample;
|
|
61
|
+
return <View {...controller} />;
|
|
48
62
|
}
|
|
49
63
|
|
|
50
|
-
export function NotActivatedPage() {
|
|
51
|
-
|
|
64
|
+
export function NotActivatedPage({ design }) {
|
|
65
|
+
var View = design || NotActivatedSample;
|
|
66
|
+
return <View />;
|
|
52
67
|
}
|
|
53
68
|
|
|
54
|
-
export function ChangePasswordPage(props) {
|
|
69
|
+
export function ChangePasswordPage({ design, ...props }) {
|
|
55
70
|
var controller = useChangePasswordController(props);
|
|
56
71
|
var ref = useDesignValidator('ChangePasswordPage', CHANGE_PASSWORD_RULES);
|
|
57
|
-
|
|
72
|
+
var View = design || ChangePasswordSample;
|
|
73
|
+
return <div ref={ref}><View {...controller} /></div>;
|
|
58
74
|
}
|
|
59
75
|
|
|
60
|
-
export function ProfilePage(props) {
|
|
76
|
+
export function ProfilePage({ design, ...props }) {
|
|
61
77
|
var controller = useProfileController(props);
|
|
62
78
|
var ref = useDesignValidator('ProfilePage', PROFILE_RULES);
|
|
63
|
-
|
|
79
|
+
var View = design || ProfileSample;
|
|
80
|
+
return <div ref={ref}><View {...controller} /></div>;
|
|
64
81
|
}
|
|
65
82
|
|
|
66
|
-
export function UserRolesPage(props) {
|
|
83
|
+
export function UserRolesPage({ design, ...props }) {
|
|
67
84
|
var controller = useUserRolesController(props);
|
|
68
85
|
var ref = useDesignValidator('UserRolesPage', USER_ROLES_MATRIX_RULES);
|
|
69
|
-
|
|
86
|
+
var View = design || UserRolesMatrixSample;
|
|
87
|
+
return <div ref={ref}><View {...controller} /></div>;
|
|
70
88
|
}
|
|
71
89
|
|
|
72
|
-
export function AccessMatrixPage(props) {
|
|
90
|
+
export function AccessMatrixPage({ design, ...props }) {
|
|
73
91
|
var controller = useAccessMatrixController(props);
|
|
74
92
|
var ref = useDesignValidator('AccessMatrixPage', ACCESS_MATRIX_RULES);
|
|
75
|
-
|
|
93
|
+
var View = design || AccessMatrixSample;
|
|
94
|
+
return <div ref={ref}><View {...controller} /></div>;
|
|
76
95
|
}
|
|
77
96
|
|
|
78
|
-
export function MasterSettingsPage(props) {
|
|
97
|
+
export function MasterSettingsPage({ design, ...props }) {
|
|
79
98
|
var controller = useMasterSettingsController(props);
|
|
80
99
|
var ref = useDesignValidator('MasterSettingsPage', MASTER_SETTINGS_RULES);
|
|
81
|
-
|
|
100
|
+
var View = design || MasterSettingsSample;
|
|
101
|
+
return <div ref={ref}><View {...controller} /></div>;
|
|
82
102
|
}
|
|
83
103
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
104
|
+
// NavPage is memoized — unlike the other pages, it lives in your app's layout
|
|
105
|
+
// route alongside <Outlet/>, so it must not re-render when the routed page
|
|
106
|
+
// changes. Exactly ONE of two things renders, never both: if `drawerItems` is
|
|
107
|
+
// non-empty, the drawer rail (NavDrawer) IS the nav — it hosts settings,
|
|
108
|
+
// notifications, and everything else that would otherwise be in the top bar.
|
|
109
|
+
// Otherwise the top bar (`design`, defaults to NavTopSample) renders alone.
|
|
110
|
+
// `navMiddle` has no equivalent in the drawer (there's no middle slot in a
|
|
111
|
+
// vertical rail) — it's simply unused whenever a drawer is present.
|
|
112
|
+
// See useNavController.js for the drawerItems/settingsOverrides/notifications
|
|
113
|
+
// props this accepts.
|
|
114
|
+
function NavPageImpl({ design, logo, expandedLogo, navMiddle, drawerPromo, ...props }) {
|
|
115
|
+
var controller = useNavController(props);
|
|
116
|
+
var ref = useDesignValidator('NavPage', NAV_RULES);
|
|
117
|
+
var TopBar = design || NavTopSample;
|
|
118
|
+
var hasDrawer = controller.drawerItems.length > 0;
|
|
119
|
+
return (
|
|
120
|
+
<div ref={ref}>
|
|
121
|
+
{hasDrawer ? (
|
|
122
|
+
<NavDrawer
|
|
123
|
+
{...controller}
|
|
124
|
+
logo={logo}
|
|
125
|
+
expandedLogo={expandedLogo}
|
|
126
|
+
drawerPromo={drawerPromo}
|
|
127
|
+
/>
|
|
128
|
+
) : (
|
|
129
|
+
<TopBar
|
|
130
|
+
{...controller}
|
|
131
|
+
logo={logo}
|
|
132
|
+
navMiddle={navMiddle}
|
|
133
|
+
/>
|
|
134
|
+
)}
|
|
135
|
+
</div>
|
|
136
|
+
);
|
|
87
137
|
}
|
|
88
138
|
|
|
89
|
-
export
|
|
90
|
-
var controller = useTenantPickerController(props);
|
|
91
|
-
return <TenantPickerSample {...controller} />;
|
|
92
|
-
}
|
|
139
|
+
export var NavPage = memo(NavPageImpl);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useState, useEffect, useRef } from 'react';
|
|
2
2
|
import { useSearchParams } from 'react-router-dom';
|
|
3
|
+
import { raiseSnackbar } from '@xeplr/ui-utils';
|
|
3
4
|
import { activateAccount } from './api.js';
|
|
4
5
|
|
|
5
6
|
export function useActivateController() {
|
|
@@ -11,17 +12,27 @@ export function useActivateController() {
|
|
|
11
12
|
var called = useRef(false);
|
|
12
13
|
|
|
13
14
|
useEffect(function() {
|
|
14
|
-
if (!token
|
|
15
|
+
if (!token) {
|
|
16
|
+
if (!called.current) {
|
|
17
|
+
called.current = true;
|
|
18
|
+
raiseSnackbar('Invalid activation link', { design: 'error' });
|
|
19
|
+
}
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (called.current) return;
|
|
15
23
|
called.current = true;
|
|
16
24
|
setLoading(true);
|
|
17
25
|
setError('');
|
|
18
26
|
setSuccess('');
|
|
19
27
|
activateAccount(token)
|
|
20
28
|
.then(function(result) {
|
|
21
|
-
|
|
29
|
+
var message = result.message || 'Account activated successfully';
|
|
30
|
+
setSuccess(message);
|
|
31
|
+
raiseSnackbar(message, { design: 'success' });
|
|
22
32
|
})
|
|
23
33
|
.catch(function(err) {
|
|
24
34
|
setError(err.message);
|
|
35
|
+
raiseSnackbar(err.message, { design: 'error' });
|
|
25
36
|
})
|
|
26
37
|
.finally(function() {
|
|
27
38
|
setLoading(false);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useState } from 'react';
|
|
2
|
+
import { raiseSnackbar } from '@xeplr/ui-utils';
|
|
2
3
|
import { changePassword } from './api.js';
|
|
3
4
|
|
|
4
5
|
export function useChangePasswordController(options = {}) {
|
|
@@ -17,20 +18,25 @@ export function useChangePasswordController(options = {}) {
|
|
|
17
18
|
setSuccess('');
|
|
18
19
|
|
|
19
20
|
if (newPassword !== confirmPassword) {
|
|
20
|
-
|
|
21
|
+
var mismatchMessage = 'Passwords do not match';
|
|
22
|
+
setError(mismatchMessage);
|
|
23
|
+
raiseSnackbar(mismatchMessage, { design: 'error' });
|
|
21
24
|
return;
|
|
22
25
|
}
|
|
23
26
|
|
|
24
27
|
setLoading(true);
|
|
25
28
|
try {
|
|
26
29
|
var result = await changePassword({ currentPassword, newPassword });
|
|
27
|
-
|
|
30
|
+
var message = 'Password changed successfully';
|
|
31
|
+
setSuccess(message);
|
|
32
|
+
raiseSnackbar(message, { design: 'success' });
|
|
28
33
|
setCurrentPassword('');
|
|
29
34
|
setNewPassword('');
|
|
30
35
|
setConfirmPassword('');
|
|
31
36
|
if (onSuccess) onSuccess(result);
|
|
32
37
|
} catch (err) {
|
|
33
38
|
setError(err.message);
|
|
39
|
+
raiseSnackbar(err.message, { design: 'error' });
|
|
34
40
|
} finally {
|
|
35
41
|
setLoading(false);
|
|
36
42
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useState } from 'react';
|
|
2
|
+
import { raiseSnackbar } from '@xeplr/ui-utils';
|
|
2
3
|
import { forgotPassword } from './api.js';
|
|
3
4
|
|
|
4
5
|
export function useForgotPasswordController() {
|
|
@@ -15,8 +16,10 @@ export function useForgotPasswordController() {
|
|
|
15
16
|
try {
|
|
16
17
|
const result = await forgotPassword({ email });
|
|
17
18
|
setSuccess(result.message);
|
|
19
|
+
raiseSnackbar(result.message, { design: 'success' });
|
|
18
20
|
} catch (err) {
|
|
19
21
|
setError(err.message);
|
|
22
|
+
raiseSnackbar(err.message, { design: 'error' });
|
|
20
23
|
} finally {
|
|
21
24
|
setLoading(false);
|
|
22
25
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useState } from 'react';
|
|
2
2
|
import { useNavigate } from 'react-router-dom';
|
|
3
|
+
import { raiseSnackbar } from '@xeplr/ui-utils';
|
|
3
4
|
import { loginUser } from './api.js';
|
|
4
5
|
import { setToken, setRefreshToken } from './token.js';
|
|
5
6
|
import { useAccess } from './AccessContext.jsx';
|
|
@@ -37,6 +38,7 @@ export function useLoginController(options = {}) {
|
|
|
37
38
|
navigate(notActivatedPath);
|
|
38
39
|
} else {
|
|
39
40
|
setError(err.message);
|
|
41
|
+
raiseSnackbar(err.message, { design: 'error' });
|
|
40
42
|
}
|
|
41
43
|
} finally {
|
|
42
44
|
setLoading(false);
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { useState, useRef, useEffect, useCallback, useMemo } from 'react';
|
|
2
|
+
import { useAccessStrict } from './AccessContext.jsx';
|
|
3
|
+
import { authPath } from './authRoutes.jsx';
|
|
4
|
+
|
|
5
|
+
// The built-in settings-dropdown items — seeded via auth's migrations (menus
|
|
6
|
+
// table, see migrations/0006_seed_catalog.sql), not hardcoded here. Maps a
|
|
7
|
+
// seeded menu NAME to the authRoutes key it links to. "Admin" is deliberately
|
|
8
|
+
// NOT here — the framework's builtin section is Profile/Change Password only;
|
|
9
|
+
// an app that wants an Admin link adds it to its own settingsOverrides. Only
|
|
10
|
+
// names present in access.menus (role-filtered, computed at login) render.
|
|
11
|
+
var BUILTIN_SETTINGS_ITEMS = [
|
|
12
|
+
{ menuName: 'Profile', authKey: 'profile' },
|
|
13
|
+
{ menuName: 'Change Password', authKey: 'changePassword' }
|
|
14
|
+
];
|
|
15
|
+
var NOTIFICATIONS_MENU_NAME = 'Notifications';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Nav is self-contained by design: it reads access/user from context (which only
|
|
19
|
+
* changes on login/logout/access updates) and owns its OWN open/closed UI state.
|
|
20
|
+
* It never depends on — and never forces a re-render of — whatever page is
|
|
21
|
+
* currently mounted in the app's <Outlet/>. Keep it that way when extending:
|
|
22
|
+
* don't lift this state into the app's layout component.
|
|
23
|
+
*
|
|
24
|
+
* @param {object} props
|
|
25
|
+
* @param {Array<{name:string, icon?:string, clickHandler?:Function, group?:string}>} [props.drawerItems] —
|
|
26
|
+
* the app's own drawer catalog (each `name` must match a `menus.name` row in
|
|
27
|
+
* the auth DB). Passing a non-empty array auto-activates the drawer rail —
|
|
28
|
+
* there's no separate flag. `group` is optional; ungrouped items render
|
|
29
|
+
* first, grouped ones under their section header (see NavDrawer.jsx).
|
|
30
|
+
* Role-filtered via access.menus, same mechanism as the settings items.
|
|
31
|
+
* @param {Array<{name:string, path:string}>} [props.settingsOverrides] — the app's
|
|
32
|
+
* OWN items rendered ABOVE the builtin (Profile/Change Password) section in
|
|
33
|
+
* the settings dropdown — this is where an "Admin" link belongs now. Each
|
|
34
|
+
* `name` must match a `menus.name` row you've seeded (in YOUR app's
|
|
35
|
+
* migrations-auth, not auth's own) — role-filtered via access.menus.
|
|
36
|
+
* @param {{count?:number, onClick?:Function}} [props.notifications] — the app's feed
|
|
37
|
+
* config. Only takes effect if the user's role grants the "Notifications" menu
|
|
38
|
+
* (seeded basic-tier, visible to everyone by default) — otherwise the bell is
|
|
39
|
+
* hidden regardless of what's passed here.
|
|
40
|
+
*/
|
|
41
|
+
export function useNavController(props) {
|
|
42
|
+
props = props || {};
|
|
43
|
+
var accessCtx = useAccessStrict();
|
|
44
|
+
var access = accessCtx.access || {};
|
|
45
|
+
var allowedMenus = access.menus || [];
|
|
46
|
+
|
|
47
|
+
var [accountOpen, setAccountOpen] = useState(false);
|
|
48
|
+
var [drawerOpen, setDrawerOpen] = useState(false);
|
|
49
|
+
|
|
50
|
+
var accountRef = useRef(null);
|
|
51
|
+
|
|
52
|
+
var toggleAccount = useCallback(function() { setAccountOpen(function(v) { return !v; }); }, []);
|
|
53
|
+
var closeAccount = useCallback(function() { setAccountOpen(false); }, []);
|
|
54
|
+
var toggleDrawer = useCallback(function() { setDrawerOpen(function(v) { return !v; }); }, []);
|
|
55
|
+
var closeDrawer = useCallback(function() { setDrawerOpen(false); }, []);
|
|
56
|
+
|
|
57
|
+
// Click-outside for the settings menu only — the drawer closes via its own overlay.
|
|
58
|
+
useEffect(function() {
|
|
59
|
+
if (!accountOpen) return;
|
|
60
|
+
function onDocClick(e) {
|
|
61
|
+
if (accountRef.current && !accountRef.current.contains(e.target)) closeAccount();
|
|
62
|
+
}
|
|
63
|
+
document.addEventListener('mousedown', onDocClick);
|
|
64
|
+
return function() { document.removeEventListener('mousedown', onDocClick); };
|
|
65
|
+
}, [accountOpen, closeAccount]);
|
|
66
|
+
|
|
67
|
+
// Settings dropdown, in render order: app overrides first, then the fixed
|
|
68
|
+
// builtin section — role-filtered the same way for both.
|
|
69
|
+
var accountItems = useMemo(function() {
|
|
70
|
+
var overrides = (props.settingsOverrides || [])
|
|
71
|
+
.filter(function(item) { return allowedMenus.indexOf(item.name) !== -1; });
|
|
72
|
+
var builtin = BUILTIN_SETTINGS_ITEMS
|
|
73
|
+
.filter(function(item) { return allowedMenus.indexOf(item.menuName) !== -1; })
|
|
74
|
+
.map(function(item) { return { name: item.menuName, path: authPath(item.authKey) }; });
|
|
75
|
+
return overrides.concat(builtin);
|
|
76
|
+
}, [allowedMenus, props.settingsOverrides]);
|
|
77
|
+
|
|
78
|
+
var notificationsAllowed = allowedMenus.indexOf(NOTIFICATIONS_MENU_NAME) !== -1;
|
|
79
|
+
|
|
80
|
+
// Role-filter the app's own drawer catalog down to what this user can actually see.
|
|
81
|
+
var drawerItems = useMemo(function() {
|
|
82
|
+
var catalog = props.drawerItems || [];
|
|
83
|
+
return catalog.filter(function(m) { return allowedMenus.indexOf(m.name) !== -1; });
|
|
84
|
+
}, [props.drawerItems, allowedMenus]);
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
user: accessCtx.user,
|
|
88
|
+
accountItems: accountItems,
|
|
89
|
+
notifications: notificationsAllowed ? (props.notifications || null) : null,
|
|
90
|
+
logout: accessCtx.logout,
|
|
91
|
+
|
|
92
|
+
accountOpen: accountOpen,
|
|
93
|
+
toggleAccount: toggleAccount,
|
|
94
|
+
closeAccount: closeAccount,
|
|
95
|
+
accountRef: accountRef,
|
|
96
|
+
|
|
97
|
+
drawerOpen: drawerOpen,
|
|
98
|
+
toggleDrawer: toggleDrawer,
|
|
99
|
+
closeDrawer: closeDrawer,
|
|
100
|
+
drawerItems: drawerItems
|
|
101
|
+
};
|
|
102
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useState, useEffect } from 'react';
|
|
2
|
+
import { raiseSnackbar } from '@xeplr/ui-utils';
|
|
2
3
|
import { getProfile, updateProfile } from './api.js';
|
|
3
4
|
|
|
4
5
|
export function useProfileController(options = {}) {
|
|
@@ -25,6 +26,7 @@ export function useProfileController(options = {}) {
|
|
|
25
26
|
});
|
|
26
27
|
} catch (err) {
|
|
27
28
|
setError(err.message);
|
|
29
|
+
raiseSnackbar(err.message, { design: 'error' });
|
|
28
30
|
} finally {
|
|
29
31
|
setFetching(false);
|
|
30
32
|
}
|
|
@@ -41,10 +43,13 @@ export function useProfileController(options = {}) {
|
|
|
41
43
|
setLoading(true);
|
|
42
44
|
try {
|
|
43
45
|
var result = await updateProfile(form);
|
|
44
|
-
|
|
46
|
+
var message = 'Profile updated successfully';
|
|
47
|
+
setSuccess(message);
|
|
48
|
+
raiseSnackbar(message, { design: 'success' });
|
|
45
49
|
if (onSuccess) onSuccess(result);
|
|
46
50
|
} catch (err) {
|
|
47
51
|
setError(err.message);
|
|
52
|
+
raiseSnackbar(err.message, { design: 'error' });
|
|
48
53
|
} finally {
|
|
49
54
|
setLoading(false);
|
|
50
55
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useState } from 'react';
|
|
2
|
+
import { raiseSnackbar } from '@xeplr/ui-utils';
|
|
2
3
|
import { registerUser } from './api.js';
|
|
3
4
|
|
|
4
5
|
export function useRegisterController(options = {}) {
|
|
@@ -20,11 +21,14 @@ export function useRegisterController(options = {}) {
|
|
|
20
21
|
setLoading(true);
|
|
21
22
|
try {
|
|
22
23
|
const result = await registerUser(form);
|
|
23
|
-
|
|
24
|
+
const message = 'Registration successful. Please wait, someone will activate you.';
|
|
25
|
+
setSuccess(message);
|
|
26
|
+
raiseSnackbar(message, { design: 'success' });
|
|
24
27
|
setForm({ name: '', email: '', phoneNumber: '', password: '' });
|
|
25
28
|
if (onSuccess) onSuccess(result);
|
|
26
29
|
} catch (err) {
|
|
27
30
|
setError(err.message);
|
|
31
|
+
raiseSnackbar(err.message, { design: 'error' });
|
|
28
32
|
} finally {
|
|
29
33
|
setLoading(false);
|
|
30
34
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { useState } from 'react';
|
|
1
|
+
import { useState, useEffect, useRef } from 'react';
|
|
2
2
|
import { useSearchParams } from 'react-router-dom';
|
|
3
|
+
import { raiseSnackbar } from '@xeplr/ui-utils';
|
|
3
4
|
import { resetPassword } from './api.js';
|
|
4
5
|
|
|
5
6
|
export function useResetPasswordController() {
|
|
@@ -9,6 +10,14 @@ export function useResetPasswordController() {
|
|
|
9
10
|
const [error, setError] = useState('');
|
|
10
11
|
const [success, setSuccess] = useState('');
|
|
11
12
|
const [loading, setLoading] = useState(false);
|
|
13
|
+
const warnedNoToken = useRef(false);
|
|
14
|
+
|
|
15
|
+
useEffect(function() {
|
|
16
|
+
if (!token && !warnedNoToken.current) {
|
|
17
|
+
warnedNoToken.current = true;
|
|
18
|
+
raiseSnackbar('Invalid or expired reset link', { design: 'error' });
|
|
19
|
+
}
|
|
20
|
+
}, [token]);
|
|
12
21
|
|
|
13
22
|
async function handleSubmit(e) {
|
|
14
23
|
e.preventDefault();
|
|
@@ -18,8 +27,10 @@ export function useResetPasswordController() {
|
|
|
18
27
|
try {
|
|
19
28
|
const result = await resetPassword({ token, newPassword: password });
|
|
20
29
|
setSuccess(result.message);
|
|
30
|
+
raiseSnackbar(result.message, { design: 'success' });
|
|
21
31
|
} catch (err) {
|
|
22
32
|
setError(err.message);
|
|
33
|
+
raiseSnackbar(err.message, { design: 'error' });
|
|
23
34
|
} finally {
|
|
24
35
|
setLoading(false);
|
|
25
36
|
}
|
package/src/validateDesign.js
CHANGED
|
@@ -99,3 +99,9 @@ export var MASTER_SETTINGS_RULES = [
|
|
|
99
99
|
{ id: 'xeplr-admin-master-search', label: 'Master search input' },
|
|
100
100
|
{ role: 'tablist', label: 'Tab list for master types' }
|
|
101
101
|
];
|
|
102
|
+
|
|
103
|
+
// Only the structure common to every Nav design (both Design 1 and Design 2
|
|
104
|
+
// have an account menu; only Design 2 has a drawer, so that isn't required here).
|
|
105
|
+
export var NAV_RULES = [
|
|
106
|
+
{ selector: '[aria-haspopup="menu"]', label: 'Account menu trigger' }
|
|
107
|
+
];
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import './auth.css';
|
|
2
|
-
|
|
3
|
-
export default function TenantPickerSample({ tenants, loading, error, label, handleSelect, onLogout }) {
|
|
4
|
-
return (
|
|
5
|
-
<div className="xeplr-auth-container">
|
|
6
|
-
<h1>Select {label}</h1>
|
|
7
|
-
{error && <div className="xeplr-auth-alert xeplr-auth-alert-error">{error}</div>}
|
|
8
|
-
{loading ? (
|
|
9
|
-
<p>Loading...</p>
|
|
10
|
-
) : tenants.length === 0 ? (
|
|
11
|
-
<div className="xeplr-auth-alert xeplr-auth-alert-error">
|
|
12
|
-
No {label.toLowerCase()}s available. Contact your administrator.
|
|
13
|
-
</div>
|
|
14
|
-
) : (
|
|
15
|
-
<div className="xeplr-tenant-picker">
|
|
16
|
-
{tenants.map(function(t) {
|
|
17
|
-
return (
|
|
18
|
-
<button
|
|
19
|
-
key={t.id}
|
|
20
|
-
type="button"
|
|
21
|
-
className="xeplr-tenant-option"
|
|
22
|
-
onClick={function() { handleSelect(t); }}
|
|
23
|
-
>
|
|
24
|
-
<span className="xeplr-tenant-name">{t.name}</span>
|
|
25
|
-
{t.code && <span className="xeplr-tenant-code">{t.code}</span>}
|
|
26
|
-
{t.description && <span className="xeplr-tenant-desc">{t.description}</span>}
|
|
27
|
-
</button>
|
|
28
|
-
);
|
|
29
|
-
})}
|
|
30
|
-
</div>
|
|
31
|
-
)}
|
|
32
|
-
{onLogout && (
|
|
33
|
-
<div className="xeplr-auth-links">
|
|
34
|
-
<p><button type="button" onClick={onLogout} style={{ background: 'none', border: 'none', color: 'var(--xeplr-text-muted, #777)', cursor: 'pointer', fontSize: '14px' }}>Sign out</button></p>
|
|
35
|
-
</div>
|
|
36
|
-
)}
|
|
37
|
-
</div>
|
|
38
|
-
);
|
|
39
|
-
}
|