@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
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
import './admin.css';
|
|
2
|
-
|
|
3
|
-
export default function TenantSample({
|
|
4
|
-
tenants, users, loading, error, saving, editingItem, editForm,
|
|
5
|
-
activeTab, setActiveTab, search, setSearch, label, labels,
|
|
6
|
-
startAdd, startEdit, cancelEdit, updateField, handleSave, handleDelete,
|
|
7
|
-
handleAssignUser, isUserAssignedTenant, reload, level
|
|
8
|
-
}) {
|
|
9
|
-
return (
|
|
10
|
-
<div className="xeplr-admin-container">
|
|
11
|
-
<div className="xeplr-admin-header">
|
|
12
|
-
<h2>{labels} Management</h2>
|
|
13
|
-
<div style={{ display: 'flex', gap: '8px' }}>
|
|
14
|
-
{activeTab === 'tenants' && (
|
|
15
|
-
<button type="button" onClick={startAdd} className="xeplr-admin-btn-primary" disabled={saving || editingItem === '__new__'}>
|
|
16
|
-
+ Add {label}
|
|
17
|
-
</button>
|
|
18
|
-
)}
|
|
19
|
-
<button type="button" onClick={reload} className="xeplr-admin-btn-secondary">Refresh</button>
|
|
20
|
-
</div>
|
|
21
|
-
</div>
|
|
22
|
-
|
|
23
|
-
<div className="xeplr-admin-tabs" role="tablist">
|
|
24
|
-
<button role="tab" className={'xeplr-admin-tab' + (activeTab === 'tenants' ? ' xeplr-admin-tab-active' : '')} onClick={function() { setActiveTab('tenants'); setSearch(''); }}>
|
|
25
|
-
{labels}
|
|
26
|
-
</button>
|
|
27
|
-
<button role="tab" className={'xeplr-admin-tab' + (activeTab === 'users' ? ' xeplr-admin-tab-active' : '')} onClick={function() { setActiveTab('users'); setSearch(''); }}>
|
|
28
|
-
Assign Users
|
|
29
|
-
</button>
|
|
30
|
-
</div>
|
|
31
|
-
|
|
32
|
-
<div className="xeplr-admin-toolbar">
|
|
33
|
-
<input type="text" placeholder={'Search ' + (activeTab === 'tenants' ? labels.toLowerCase() : 'users') + '...'} value={search} onChange={function(e) { setSearch(e.target.value); }} className="xeplr-admin-search" />
|
|
34
|
-
</div>
|
|
35
|
-
|
|
36
|
-
{error && <div className="xeplr-admin-alert xeplr-admin-alert-error">{error}</div>}
|
|
37
|
-
|
|
38
|
-
{loading ? (
|
|
39
|
-
<div className="xeplr-admin-loading">Loading...</div>
|
|
40
|
-
) : activeTab === 'tenants' ? (
|
|
41
|
-
renderTenantList(tenants, editingItem, editForm, saving, label, startEdit, cancelEdit, updateField, handleSave, handleDelete)
|
|
42
|
-
) : (
|
|
43
|
-
renderUserAssignment(users, tenants, search, handleAssignUser, isUserAssignedTenant, label)
|
|
44
|
-
)}
|
|
45
|
-
</div>
|
|
46
|
-
);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function renderTenantList(tenants, editingItem, editForm, saving, label, startEdit, cancelEdit, updateField, handleSave, handleDelete) {
|
|
50
|
-
return (
|
|
51
|
-
<div className="xeplr-admin-matrix-wrapper">
|
|
52
|
-
<table className="xeplr-admin-matrix xeplr-admin-master-table">
|
|
53
|
-
<thead>
|
|
54
|
-
<tr>
|
|
55
|
-
<th className="xeplr-admin-sticky-col">Name</th>
|
|
56
|
-
<th>Code</th>
|
|
57
|
-
<th>Description</th>
|
|
58
|
-
<th className="xeplr-admin-actions-col">Actions</th>
|
|
59
|
-
</tr>
|
|
60
|
-
</thead>
|
|
61
|
-
<tbody>
|
|
62
|
-
{editingItem === '__new__' && (
|
|
63
|
-
<tr className="xeplr-admin-editing-row">
|
|
64
|
-
<td className="xeplr-admin-sticky-col">
|
|
65
|
-
<input type="text" className="xeplr-admin-inline-input" value={editForm.name || ''} onChange={function(e) { updateField('name', e.target.value); }} placeholder={label + ' name'} autoFocus />
|
|
66
|
-
</td>
|
|
67
|
-
<td>
|
|
68
|
-
<input type="text" className="xeplr-admin-inline-input xeplr-admin-group-input" value={editForm.code || ''} onChange={function(e) { updateField('code', e.target.value); }} placeholder="Short code" />
|
|
69
|
-
</td>
|
|
70
|
-
<td>
|
|
71
|
-
<input type="text" className="xeplr-admin-inline-input" value={editForm.description || ''} onChange={function(e) { updateField('description', e.target.value); }} placeholder="Description" />
|
|
72
|
-
</td>
|
|
73
|
-
<td className="xeplr-admin-actions-col">
|
|
74
|
-
<div className="xeplr-admin-action-btns">
|
|
75
|
-
<button onClick={handleSave} disabled={saving} className="xeplr-admin-btn-save" title="Save">{'\u2713'}</button>
|
|
76
|
-
<button onClick={cancelEdit} className="xeplr-admin-btn-cancel" title="Cancel">{'\u2715'}</button>
|
|
77
|
-
</div>
|
|
78
|
-
</td>
|
|
79
|
-
</tr>
|
|
80
|
-
)}
|
|
81
|
-
{tenants.length === 0 && editingItem !== '__new__' ? (
|
|
82
|
-
<tr><td colSpan="4" className="xeplr-admin-empty">No {label.toLowerCase()}s found</td></tr>
|
|
83
|
-
) : (
|
|
84
|
-
tenants.map(function(t) {
|
|
85
|
-
var isEditing = editingItem === t.id;
|
|
86
|
-
return (
|
|
87
|
-
<tr key={t.id} className={isEditing ? 'xeplr-admin-editing-row' : ''}>
|
|
88
|
-
<td className="xeplr-admin-sticky-col">
|
|
89
|
-
{isEditing ? (
|
|
90
|
-
<input type="text" className="xeplr-admin-inline-input" value={editForm.name || ''} onChange={function(e) { updateField('name', e.target.value); }} />
|
|
91
|
-
) : (
|
|
92
|
-
<span className="xeplr-admin-item-name">{t.name}</span>
|
|
93
|
-
)}
|
|
94
|
-
</td>
|
|
95
|
-
<td>
|
|
96
|
-
{isEditing ? (
|
|
97
|
-
<input type="text" className="xeplr-admin-inline-input xeplr-admin-group-input" value={editForm.code || ''} onChange={function(e) { updateField('code', e.target.value); }} />
|
|
98
|
-
) : (
|
|
99
|
-
t.code ? <span className="xeplr-admin-group-tag">{t.code}</span> : '\u2014'
|
|
100
|
-
)}
|
|
101
|
-
</td>
|
|
102
|
-
<td>
|
|
103
|
-
{isEditing ? (
|
|
104
|
-
<input type="text" className="xeplr-admin-inline-input" value={editForm.description || ''} onChange={function(e) { updateField('description', e.target.value); }} />
|
|
105
|
-
) : (
|
|
106
|
-
t.description || '\u2014'
|
|
107
|
-
)}
|
|
108
|
-
</td>
|
|
109
|
-
<td className="xeplr-admin-actions-col">
|
|
110
|
-
<div className="xeplr-admin-action-btns">
|
|
111
|
-
{isEditing ? (
|
|
112
|
-
<>
|
|
113
|
-
<button onClick={handleSave} className="xeplr-admin-btn-save" title="Save">{'\u2713'}</button>
|
|
114
|
-
<button onClick={cancelEdit} className="xeplr-admin-btn-cancel" title="Cancel">{'\u2715'}</button>
|
|
115
|
-
</>
|
|
116
|
-
) : (
|
|
117
|
-
<>
|
|
118
|
-
<button onClick={function() { startEdit(t); }} className="xeplr-admin-btn-edit" title="Edit" disabled={editingItem !== null}>{'\u270E'}</button>
|
|
119
|
-
<button onClick={function() { if (confirm('Delete this ' + label.toLowerCase() + '?')) handleDelete(t.id); }} className="xeplr-admin-btn-delete" title="Delete" disabled={editingItem !== null}>{'\u2715'}</button>
|
|
120
|
-
</>
|
|
121
|
-
)}
|
|
122
|
-
</div>
|
|
123
|
-
</td>
|
|
124
|
-
</tr>
|
|
125
|
-
);
|
|
126
|
-
})
|
|
127
|
-
)}
|
|
128
|
-
</tbody>
|
|
129
|
-
</table>
|
|
130
|
-
</div>
|
|
131
|
-
);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function renderUserAssignment(users, tenants, search, handleAssignUser, isUserAssignedTenant, label) {
|
|
135
|
-
var q = search ? search.toLowerCase() : '';
|
|
136
|
-
var filtered = users.filter(function(u) {
|
|
137
|
-
if (!q) return true;
|
|
138
|
-
return (u.name && u.name.toLowerCase().includes(q)) || (u.email && u.email.toLowerCase().includes(q));
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
return (
|
|
142
|
-
<div className="xeplr-admin-matrix-wrapper">
|
|
143
|
-
<table className="xeplr-admin-matrix" role="grid">
|
|
144
|
-
<thead>
|
|
145
|
-
<tr>
|
|
146
|
-
<th className="xeplr-admin-sticky-col">User</th>
|
|
147
|
-
{tenants.map(function(t) {
|
|
148
|
-
return <th key={t.id} className="xeplr-admin-role-header">{t.name}</th>;
|
|
149
|
-
})}
|
|
150
|
-
</tr>
|
|
151
|
-
</thead>
|
|
152
|
-
<tbody>
|
|
153
|
-
{filtered.length === 0 ? (
|
|
154
|
-
<tr><td colSpan={tenants.length + 1} className="xeplr-admin-empty">No users found</td></tr>
|
|
155
|
-
) : (
|
|
156
|
-
filtered.map(function(u) {
|
|
157
|
-
return (
|
|
158
|
-
<tr key={u.id}>
|
|
159
|
-
<td className="xeplr-admin-sticky-col" style={{ lineHeight: '1.3' }}>
|
|
160
|
-
<div className="xeplr-admin-user-name">{u.name || '\u2014'}</div>
|
|
161
|
-
<div className="xeplr-admin-user-email">{u.email}</div>
|
|
162
|
-
</td>
|
|
163
|
-
{tenants.map(function(t) {
|
|
164
|
-
var assigned = isUserAssignedTenant(u, t.id);
|
|
165
|
-
return (
|
|
166
|
-
<td key={t.id} className="xeplr-admin-cell">
|
|
167
|
-
<label className="xeplr-admin-toggle">
|
|
168
|
-
<input type="checkbox" checked={assigned} onChange={function() { handleAssignUser(u.id, t.id, assigned); }} />
|
|
169
|
-
<span className="xeplr-admin-checkmark" />
|
|
170
|
-
</label>
|
|
171
|
-
</td>
|
|
172
|
-
);
|
|
173
|
-
})}
|
|
174
|
-
</tr>
|
|
175
|
-
);
|
|
176
|
-
})
|
|
177
|
-
)}
|
|
178
|
-
</tbody>
|
|
179
|
-
</table>
|
|
180
|
-
</div>
|
|
181
|
-
);
|
|
182
|
-
}
|
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect, useCallback } from 'react';
|
|
2
|
-
import { getTenants, saveTenant, deleteTenant, getUsers, assignUserTenant } from './adminApi.js';
|
|
3
|
-
|
|
4
|
-
export function useTenantController(options = {}) {
|
|
5
|
-
var [tenants, setTenants] = useState([]);
|
|
6
|
-
var [users, setUsers] = useState([]);
|
|
7
|
-
var [loading, setLoading] = useState(true);
|
|
8
|
-
var [error, setError] = useState('');
|
|
9
|
-
var [saving, setSaving] = useState(false);
|
|
10
|
-
var [editingItem, setEditingItem] = useState(null);
|
|
11
|
-
var [editForm, setEditForm] = useState({});
|
|
12
|
-
var [activeTab, setActiveTab] = useState('tenants');
|
|
13
|
-
var [search, setSearch] = useState('');
|
|
14
|
-
|
|
15
|
-
var level = options.level || 1;
|
|
16
|
-
var label = options.label || 'Company';
|
|
17
|
-
var labels = options.labels || 'Companies';
|
|
18
|
-
|
|
19
|
-
useEffect(function() {
|
|
20
|
-
loadData();
|
|
21
|
-
}, []);
|
|
22
|
-
|
|
23
|
-
async function loadData() {
|
|
24
|
-
setLoading(true);
|
|
25
|
-
setError('');
|
|
26
|
-
try {
|
|
27
|
-
var [tenantData, userData] = await Promise.all([
|
|
28
|
-
getTenants(level),
|
|
29
|
-
getUsers()
|
|
30
|
-
]);
|
|
31
|
-
setTenants(tenantData);
|
|
32
|
-
setUsers(userData);
|
|
33
|
-
} catch (err) {
|
|
34
|
-
setError(err.message);
|
|
35
|
-
} finally {
|
|
36
|
-
setLoading(false);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
var filteredTenants = tenants.filter(function(t) {
|
|
41
|
-
if (!search) return true;
|
|
42
|
-
var q = search.toLowerCase();
|
|
43
|
-
return (t.name && t.name.toLowerCase().includes(q)) ||
|
|
44
|
-
(t.code && t.code.toLowerCase().includes(q));
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
function startAdd() {
|
|
48
|
-
setEditForm({ name: '', code: '', description: '' });
|
|
49
|
-
setEditingItem('__new__');
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function startEdit(item) {
|
|
53
|
-
setEditForm({ name: item.name, code: item.code || '', description: item.description || '' });
|
|
54
|
-
setEditingItem(item.id);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function cancelEdit() {
|
|
58
|
-
setEditingItem(null);
|
|
59
|
-
setEditForm({});
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function updateField(key, value) {
|
|
63
|
-
setEditForm(function(prev) { var next = { ...prev }; next[key] = value; return next; });
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
async function handleSave() {
|
|
67
|
-
setSaving(true);
|
|
68
|
-
setError('');
|
|
69
|
-
try {
|
|
70
|
-
var data = { ...editForm, level: level };
|
|
71
|
-
if (editingItem !== '__new__') data.id = editingItem;
|
|
72
|
-
var result = await saveTenant(data);
|
|
73
|
-
if (editingItem === '__new__') {
|
|
74
|
-
data.id = result.id;
|
|
75
|
-
setTenants(function(prev) { return prev.concat([data]); });
|
|
76
|
-
} else {
|
|
77
|
-
setTenants(function(prev) {
|
|
78
|
-
return prev.map(function(t) { return t.id === editingItem ? { ...t, ...data } : t; });
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
setEditingItem(null);
|
|
82
|
-
setEditForm({});
|
|
83
|
-
} catch (err) {
|
|
84
|
-
setError(err.message);
|
|
85
|
-
} finally {
|
|
86
|
-
setSaving(false);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
async function handleDelete(id) {
|
|
91
|
-
setSaving(true);
|
|
92
|
-
setError('');
|
|
93
|
-
try {
|
|
94
|
-
await deleteTenant(id);
|
|
95
|
-
setTenants(function(prev) { return prev.filter(function(t) { return t.id !== id; }); });
|
|
96
|
-
} catch (err) {
|
|
97
|
-
setError(err.message);
|
|
98
|
-
} finally {
|
|
99
|
-
setSaving(false);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
async function handleAssignUser(userId, tenantId, currentlyAssigned) {
|
|
104
|
-
setError('');
|
|
105
|
-
try {
|
|
106
|
-
await assignUserTenant({ userId, tenantId, assign: !currentlyAssigned });
|
|
107
|
-
// Reload to get fresh user-tenant mappings
|
|
108
|
-
var userData = await getUsers();
|
|
109
|
-
setUsers(userData);
|
|
110
|
-
} catch (err) {
|
|
111
|
-
setError(err.message);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function isUserAssignedTenant(user, tenantId) {
|
|
116
|
-
return user.tenants && user.tenants.some(function(t) { return t.id === tenantId; });
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
var reload = useCallback(function() { loadData(); }, []);
|
|
120
|
-
|
|
121
|
-
return {
|
|
122
|
-
tenants: filteredTenants,
|
|
123
|
-
users,
|
|
124
|
-
loading,
|
|
125
|
-
error,
|
|
126
|
-
saving,
|
|
127
|
-
editingItem,
|
|
128
|
-
editForm,
|
|
129
|
-
activeTab,
|
|
130
|
-
setActiveTab,
|
|
131
|
-
search,
|
|
132
|
-
setSearch,
|
|
133
|
-
level,
|
|
134
|
-
label,
|
|
135
|
-
labels,
|
|
136
|
-
startAdd,
|
|
137
|
-
startEdit,
|
|
138
|
-
cancelEdit,
|
|
139
|
-
updateField,
|
|
140
|
-
handleSave,
|
|
141
|
-
handleDelete,
|
|
142
|
-
handleAssignUser,
|
|
143
|
-
isUserAssignedTenant,
|
|
144
|
-
reload,
|
|
145
|
-
};
|
|
146
|
-
}
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect } from 'react';
|
|
2
|
-
import { getMyTenants, logoutUser } from './api.js';
|
|
3
|
-
import { clearAuth } from './token.js';
|
|
4
|
-
|
|
5
|
-
var STORAGE_KEY = 'xeplr:activeTenant';
|
|
6
|
-
var COUNT_KEY = 'xeplr:tenantCount';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Get the currently selected tenant from localStorage.
|
|
10
|
-
*/
|
|
11
|
-
export function getActiveTenant() {
|
|
12
|
-
try {
|
|
13
|
-
var raw = localStorage.getItem(STORAGE_KEY);
|
|
14
|
-
return raw ? JSON.parse(raw) : null;
|
|
15
|
-
} catch (e) {
|
|
16
|
-
return null;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Set the active tenant in localStorage.
|
|
22
|
-
*/
|
|
23
|
-
export function setActiveTenant(tenant) {
|
|
24
|
-
if (tenant) {
|
|
25
|
-
localStorage.setItem(STORAGE_KEY, JSON.stringify(tenant));
|
|
26
|
-
} else {
|
|
27
|
-
localStorage.removeItem(STORAGE_KEY);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Clear the active tenant (on logout).
|
|
33
|
-
*/
|
|
34
|
-
export function clearActiveTenant() {
|
|
35
|
-
localStorage.removeItem(STORAGE_KEY);
|
|
36
|
-
localStorage.removeItem(COUNT_KEY);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Check if the user has multiple tenants (show switch option).
|
|
41
|
-
*/
|
|
42
|
-
export function hasMultipleTenants() {
|
|
43
|
-
try {
|
|
44
|
-
return parseInt(localStorage.getItem(COUNT_KEY) || '0') > 1;
|
|
45
|
-
} catch (e) {
|
|
46
|
-
return false;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Controller for the tenant picker screen shown after login.
|
|
52
|
-
*/
|
|
53
|
-
export function useTenantPickerController(options = {}) {
|
|
54
|
-
var [tenants, setTenants] = useState([]);
|
|
55
|
-
var [loading, setLoading] = useState(true);
|
|
56
|
-
var [error, setError] = useState('');
|
|
57
|
-
var level = options.level || 1;
|
|
58
|
-
var label = options.label || 'Company';
|
|
59
|
-
|
|
60
|
-
useEffect(function() {
|
|
61
|
-
setLoading(true);
|
|
62
|
-
getMyTenants()
|
|
63
|
-
.then(function(data) {
|
|
64
|
-
setTenants(data);
|
|
65
|
-
try { localStorage.setItem(COUNT_KEY, String(data.length)); } catch(e) {}
|
|
66
|
-
// Auto-select if only one tenant
|
|
67
|
-
if (data.length === 1) {
|
|
68
|
-
setActiveTenant(data[0]);
|
|
69
|
-
if (options.onSelect) options.onSelect(data[0]);
|
|
70
|
-
}
|
|
71
|
-
})
|
|
72
|
-
.catch(function(err) { setError(err.message); })
|
|
73
|
-
.finally(function() { setLoading(false); });
|
|
74
|
-
}, [level]);
|
|
75
|
-
|
|
76
|
-
function handleSelect(tenant) {
|
|
77
|
-
setActiveTenant(tenant);
|
|
78
|
-
if (options.onSelect) options.onSelect(tenant);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function handleLogout() {
|
|
82
|
-
clearActiveTenant();
|
|
83
|
-
logoutUser();
|
|
84
|
-
clearAuth();
|
|
85
|
-
if (options.onLogout) options.onLogout();
|
|
86
|
-
else window.location.href = '/login';
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return {
|
|
90
|
-
tenants,
|
|
91
|
-
loading,
|
|
92
|
-
error,
|
|
93
|
-
label,
|
|
94
|
-
handleSelect,
|
|
95
|
-
onLogout: handleLogout
|
|
96
|
-
};
|
|
97
|
-
}
|