@micha.bigler/ui-core-micha 3.7.0 → 3.9.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/CHANGELOG.md +25 -0
- package/DESIGN.md +8 -0
- package/WORK_ORDERS.md +2 -0
- package/dist/auth/apiClient.js +8 -0
- package/dist/components/AccessCodeManager.js +5 -5
- package/dist/components/AccessCodeSingleUseToggle.js +3 -2
- package/dist/components/AllowedEmailDomainsManager.js +3 -2
- package/dist/components/AuthFactorRequirementCard.js +3 -2
- package/dist/components/BulkInviteCsvTab.js +3 -2
- package/dist/components/MFAComponent.js +9 -8
- package/dist/components/MfaLoginComponent.js +7 -6
- package/dist/components/PasskeysComponent.js +7 -6
- package/dist/components/ProfileComponent.js +4 -3
- package/dist/components/QrSignupManager.js +4 -3
- package/dist/components/QrSignupValidityManager.js +3 -2
- package/dist/components/RegistrationMethodsManager.js +3 -2
- package/dist/components/SecurityComponent.js +5 -4
- package/dist/components/SupportRecoveryRequestsTab.js +8 -7
- package/dist/components/UserInviteComponent.js +3 -2
- package/dist/components/UserListComponent.js +7 -6
- package/dist/i18n/authTranslations.js +420 -0
- package/dist/pages/AccountPage.js +3 -2
- package/dist/pages/LoginPage.js +10 -9
- package/dist/pages/PasswordChangePage.js +4 -3
- package/dist/pages/PasswordInvitePage.js +7 -6
- package/dist/pages/PasswordResetRequestPage.js +5 -4
- package/dist/pages/SignUpPage.js +7 -6
- package/dist/pages/SignupConfirmPage.js +7 -6
- package/dist/theme/createAppTheme.js +62 -9
- package/dist/theme/tokens.js +1 -1
- package/dist/utils/auth-errors.js +45 -8
- package/dist/utils/resolveErrorText.js +24 -0
- package/package.json +1 -1
- package/src/auth/apiClient.jsx +9 -0
- package/src/components/AccessCodeManager.jsx +5 -5
- package/src/components/AccessCodeSingleUseToggle.jsx +3 -2
- package/src/components/AllowedEmailDomainsManager.jsx +3 -2
- package/src/components/AuthFactorRequirementCard.jsx +3 -2
- package/src/components/BulkInviteCsvTab.jsx +3 -2
- package/src/components/MFAComponent.jsx +9 -8
- package/src/components/MfaLoginComponent.jsx +7 -6
- package/src/components/PasskeysComponent.jsx +7 -6
- package/src/components/ProfileComponent.jsx +4 -3
- package/src/components/QrSignupManager.jsx +4 -3
- package/src/components/QrSignupValidityManager.jsx +3 -2
- package/src/components/RegistrationMethodsManager.jsx +3 -2
- package/src/components/SecurityComponent.jsx +5 -4
- package/src/components/SupportRecoveryRequestsTab.jsx +9 -8
- package/src/components/UserInviteComponent.jsx +3 -2
- package/src/components/UserListComponent.jsx +7 -6
- package/src/i18n/authTranslations.ts +421 -0
- package/src/pages/AccountPage.jsx +3 -4
- package/src/pages/LoginPage.jsx +10 -9
- package/src/pages/PasswordChangePage.jsx +4 -3
- package/src/pages/PasswordInvitePage.jsx +7 -6
- package/src/pages/PasswordResetRequestPage.jsx +5 -4
- package/src/pages/SignUpPage.jsx +7 -6
- package/src/pages/SignupConfirmPage.jsx +7 -6
- package/src/theme/createAppTheme.js +68 -4
- package/src/theme/tokens.js +1 -1
- package/src/utils/auth-errors.js +44 -7
- package/src/utils/resolveErrorText.js +28 -0
- package/tests/AccountPage.test.jsx +27 -20
- package/tests/PasswordInvitePage.test.jsx +28 -21
- package/tests/QrSignupManager.registrationContext.test.jsx +5 -1
- package/tests/SignUpPage.test.jsx +14 -3
- package/tests/UserListComponent.test.jsx +22 -15
- package/tests/apiClientLanguage.test.js +33 -0
- package/tests/authErrors.test.js +31 -0
- package/tests/createAppThemePageContrast.test.js +108 -0
- package/tests/errorCallSiteGuard.test.js +32 -0
- package/tests/loginPageErrorResolution.test.jsx +102 -0
- package/tests/resolveErrorText.test.js +72 -0
- package/work-orders/UCM-AUTH-10.md +490 -0
- package/work-orders/UCM-THEME-14.md +3 -1
- package/work-orders/UCM-THEME-15.md +356 -0
|
@@ -10,10 +10,11 @@ import { MFAComponent } from './MFAComponent';
|
|
|
10
10
|
import { changePassword } from '../auth/authApi';
|
|
11
11
|
import { startSocialLogin } from '../utils/authService';
|
|
12
12
|
import { AuthContext } from '../auth/AuthContext';
|
|
13
|
+
import { resolveErrorText } from '../utils/resolveErrorText';
|
|
13
14
|
export const SECURITY_ALERT_SX = { mb: 2 };
|
|
14
15
|
export const SECURITY_DIVIDER_SX = { my: 3 };
|
|
15
16
|
export function SecurityComponent({ fromRecovery = false, fromWeakLogin = false, }) {
|
|
16
|
-
const { t } = useTranslation();
|
|
17
|
+
const { t, i18n } = useTranslation();
|
|
17
18
|
const { authMethods } = useContext(AuthContext);
|
|
18
19
|
const [messageKey, setMessageKey] = useState(null);
|
|
19
20
|
const [errorKey, setErrorKey] = useState(null);
|
|
@@ -44,7 +45,7 @@ export function SecurityComponent({ fromRecovery = false, fromWeakLogin = false,
|
|
|
44
45
|
});
|
|
45
46
|
}
|
|
46
47
|
catch (err) {
|
|
47
|
-
setErrorKey(err
|
|
48
|
+
setErrorKey(resolveErrorText(i18n, err, 'Auth.SOCIAL_LOGIN_FAILED'));
|
|
48
49
|
}
|
|
49
50
|
};
|
|
50
51
|
const handlePasswordChange = async (currentPassword, newPassword) => {
|
|
@@ -55,9 +56,9 @@ export function SecurityComponent({ fromRecovery = false, fromWeakLogin = false,
|
|
|
55
56
|
setMessageKey('Auth.RESET_PASSWORD_SUCCESS');
|
|
56
57
|
}
|
|
57
58
|
catch (err) {
|
|
58
|
-
setErrorKey(err
|
|
59
|
+
setErrorKey(resolveErrorText(i18n, err, 'Auth.PASSWORD_CHANGE_FAILED'));
|
|
59
60
|
}
|
|
60
61
|
};
|
|
61
|
-
return (_jsxs(Box, { children: [fromRecovery && (_jsx(Alert, { severity: "warning", sx: SECURITY_ALERT_SX, children: t('Security.RECOVERY_LOGIN_WARNING') })), fromWeakLogin && (_jsx(Alert, { severity: "warning", sx: SECURITY_ALERT_SX, children: t('Security.WEAK_LOGIN_WARNING') })), messageKey && (_jsx(Alert, { severity: "success", sx: SECURITY_ALERT_SX, children: t(messageKey) })), errorKey && (_jsx(Alert, { severity: "error", sx: SECURITY_ALERT_SX, children:
|
|
62
|
+
return (_jsxs(Box, { children: [fromRecovery && (_jsx(Alert, { severity: "warning", sx: SECURITY_ALERT_SX, children: t('Security.RECOVERY_LOGIN_WARNING') })), fromWeakLogin && (_jsx(Alert, { severity: "warning", sx: SECURITY_ALERT_SX, children: t('Security.WEAK_LOGIN_WARNING') })), messageKey && (_jsx(Alert, { severity: "success", sx: SECURITY_ALERT_SX, children: t(messageKey) })), errorKey && (_jsx(Alert, { severity: "error", sx: SECURITY_ALERT_SX, children: errorKey })), canChangePassword && (_jsxs(_Fragment, { children: [_jsx(Typography, { variant: "h6", gutterBottom: true, children: t('Auth.LOGIN_PASSWORD_LABEL') }), _jsx(PasswordChangeForm, { onSubmit: handlePasswordChange }), needsDividerAfter('password') && _jsx(Divider, { sx: SECURITY_DIVIDER_SX })] })), socialLoginEnabled && (_jsxs(_Fragment, { children: [_jsx(Typography, { variant: "h6", gutterBottom: true, children: t('Security.SOCIAL_SECTION_TITLE') }), _jsx(Typography, { variant: "body2", sx: { mb: 1 }, children: t('Security.SOCIAL_SECTION_DESCRIPTION') }), _jsx(SocialLoginButtons, { onProviderClick: handleSocialClick, providers: socialProviders }), needsDividerAfter('social') && _jsx(Divider, { sx: SECURITY_DIVIDER_SX })] })), passkeysEnabled && (_jsxs(_Fragment, { children: [_jsx(PasskeysComponent, {}), needsDividerAfter('passkeys') && _jsx(Divider, { sx: SECURITY_DIVIDER_SX })] })), mfaEnabled && _jsx(MFAComponent, {})] }));
|
|
62
63
|
}
|
|
63
64
|
;
|
|
@@ -4,6 +4,7 @@ import React, { useContext, useEffect, useState } from 'react';
|
|
|
4
4
|
import { Alert, Autocomplete, Box, Button, CircularProgress, Dialog, DialogActions, DialogContent, DialogTitle, IconButton, Paper, Stack, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TextField, Typography, } from '@mui/material';
|
|
5
5
|
import CloseIcon from '@mui/icons-material/Close';
|
|
6
6
|
import { useTranslation } from 'react-i18next';
|
|
7
|
+
import { resolveErrorText } from '../utils/resolveErrorText';
|
|
7
8
|
import { AuthContext } from '../auth/AuthContext';
|
|
8
9
|
import { approveRecoveryRequest, fetchRecoveryRequests, fetchUsersList, rejectRecoveryRequest, updateUserSupportStatus, } from '../auth/authApi';
|
|
9
10
|
export const SUPPORT_RECOVERY_AGENT_PAPER_SX = { p: 2, mb: 3 };
|
|
@@ -33,7 +34,7 @@ export function SupportRecoveryRequestsTab() {
|
|
|
33
34
|
setRequests(Array.isArray(data) ? data : []);
|
|
34
35
|
}
|
|
35
36
|
catch (err) {
|
|
36
|
-
setErrorKey(err
|
|
37
|
+
setErrorKey(resolveErrorText(i18n, err, 'Support.RECOVERY_REQUESTS_LOAD_FAILED'));
|
|
37
38
|
}
|
|
38
39
|
finally {
|
|
39
40
|
setLoading(false);
|
|
@@ -58,7 +59,7 @@ export function SupportRecoveryRequestsTab() {
|
|
|
58
59
|
setAllUsers(list);
|
|
59
60
|
}
|
|
60
61
|
catch (err) {
|
|
61
|
-
setAgentErrorKey(err
|
|
62
|
+
setAgentErrorKey(resolveErrorText(i18n, err, 'Auth.USER_LIST_FAILED'));
|
|
62
63
|
}
|
|
63
64
|
finally {
|
|
64
65
|
setAgentLoading(false);
|
|
@@ -92,7 +93,7 @@ export function SupportRecoveryRequestsTab() {
|
|
|
92
93
|
closeDialog();
|
|
93
94
|
}
|
|
94
95
|
catch (err) {
|
|
95
|
-
setErrorKey(err
|
|
96
|
+
setErrorKey(resolveErrorText(i18n, err, 'Support.RECOVERY_REQUEST_APPROVE_FAILED'));
|
|
96
97
|
}
|
|
97
98
|
};
|
|
98
99
|
const handleReject = async () => {
|
|
@@ -105,7 +106,7 @@ export function SupportRecoveryRequestsTab() {
|
|
|
105
106
|
closeDialog();
|
|
106
107
|
}
|
|
107
108
|
catch (err) {
|
|
108
|
-
setErrorKey(err
|
|
109
|
+
setErrorKey(resolveErrorText(i18n, err, 'Support.RECOVERY_REQUEST_REJECT_FAILED'));
|
|
109
110
|
}
|
|
110
111
|
};
|
|
111
112
|
const getUserDisplayName = (entry) => {
|
|
@@ -135,7 +136,7 @@ export function SupportRecoveryRequestsTab() {
|
|
|
135
136
|
await loadUsers();
|
|
136
137
|
}
|
|
137
138
|
catch (err) {
|
|
138
|
-
setAgentErrorKey(err
|
|
139
|
+
setAgentErrorKey(resolveErrorText(i18n, err, 'Auth.USER_SUPPORT_UPDATE_FAILED'));
|
|
139
140
|
}
|
|
140
141
|
finally {
|
|
141
142
|
setAgentActionUserId(null);
|
|
@@ -154,7 +155,7 @@ export function SupportRecoveryRequestsTab() {
|
|
|
154
155
|
await loadUsers();
|
|
155
156
|
}
|
|
156
157
|
catch (err) {
|
|
157
|
-
setAgentErrorKey(err
|
|
158
|
+
setAgentErrorKey(resolveErrorText(i18n, err, 'Auth.USER_SUPPORT_UPDATE_FAILED'));
|
|
158
159
|
}
|
|
159
160
|
finally {
|
|
160
161
|
setAgentActionUserId(null);
|
|
@@ -163,7 +164,7 @@ export function SupportRecoveryRequestsTab() {
|
|
|
163
164
|
if (loading) {
|
|
164
165
|
return (_jsx(Box, { sx: { display: 'flex', justifyContent: 'center', mt: 4 }, children: _jsx(CircularProgress, {}) }));
|
|
165
166
|
}
|
|
166
|
-
return (_jsxs(Box, { children: [canManageAgents && (_jsxs(Paper, { variant: "outlined", sx: SUPPORT_RECOVERY_AGENT_PAPER_SX, children: [_jsx(Typography, { variant: "h6", gutterBottom: true, children: t('Support.AGENTS_TITLE', 'Support agents') }), _jsx(Typography, { variant: "body2", sx: { mb: 2 }, children: t('Support.AGENTS_DESCRIPTION', 'Assign which users are allowed to handle support requests.') }), agentErrorKey && (_jsx(Alert, { severity: "error", sx: SUPPORT_RECOVERY_ALERT_SX, children:
|
|
167
|
+
return (_jsxs(Box, { children: [canManageAgents && (_jsxs(Paper, { variant: "outlined", sx: SUPPORT_RECOVERY_AGENT_PAPER_SX, children: [_jsx(Typography, { variant: "h6", gutterBottom: true, children: t('Support.AGENTS_TITLE', 'Support agents') }), _jsx(Typography, { variant: "body2", sx: { mb: 2 }, children: t('Support.AGENTS_DESCRIPTION', 'Assign which users are allowed to handle support requests.') }), agentErrorKey && (_jsx(Alert, { severity: "error", sx: SUPPORT_RECOVERY_ALERT_SX, children: agentErrorKey })), _jsxs(Stack, { direction: { xs: 'column', md: 'row' }, spacing: 2, sx: { mb: 2, alignItems: { md: 'flex-start' } }, children: [_jsx(Autocomplete, { fullWidth: true, options: availableAgentCandidates, value: selectedAgentCandidate, loading: agentLoading, onChange: (_event, value) => setSelectedAgentCandidate(value), isOptionEqualToValue: (option, value) => option.id === value.id, getOptionLabel: getUserOptionLabel, renderInput: (params) => (_jsx(TextField, Object.assign({}, params, { label: t('Support.AGENTS_SELECT_LABEL', 'Select user'), placeholder: t('Support.AGENTS_SELECT_PLACEHOLDER', 'Choose a user') }))) }), _jsx(Button, { variant: "contained", onClick: handleAddAgent, disabled: !selectedAgentCandidate || agentLoading || Boolean(agentActionUserId), sx: SUPPORT_RECOVERY_AGENT_BUTTON_SX, children: t('Support.AGENTS_ADD', 'Add as agent') })] }), agentLoading ? (_jsx(Box, { sx: { display: 'flex', justifyContent: 'center', py: 3 }, children: _jsx(CircularProgress, { size: 24 }) })) : supportAgents.length === 0 ? (_jsx(Typography, { variant: "body2", children: t('Support.AGENTS_EMPTY', 'No support agents assigned yet.') })) : (_jsx(TableContainer, { component: Paper, variant: "outlined", children: _jsxs(Table, { size: "small", children: [_jsx(TableHead, { children: _jsxs(TableRow, { children: [_jsx(TableCell, { children: t('Profile.NAME_LABEL', 'Name') }), _jsx(TableCell, { children: t('Auth.EMAIL_LABEL', 'Email') }), _jsx(TableCell, { align: "right", children: t('Common.ACTIONS', 'Actions') })] }) }), _jsx(TableBody, { children: supportAgents.map((agentUser) => (_jsxs(TableRow, { children: [_jsx(TableCell, { children: getUserDisplayName(agentUser) || '-' }), _jsx(TableCell, { children: agentUser.email || '-' }), _jsx(TableCell, { align: "right", children: _jsx(IconButton, { size: "small", color: "error", onClick: () => handleRemoveAgent(agentUser), disabled: agentActionUserId === agentUser.id, "aria-label": t('Support.AGENTS_REMOVE', 'Remove support agent'), children: _jsx(CloseIcon, { fontSize: "small" }) }) })] }, agentUser.id))) })] }) }))] })), _jsx(Typography, { variant: "h6", gutterBottom: true, children: t('Support.RECOVERY_REQUESTS_TITLE', 'Account recovery requests') }), _jsx(Typography, { variant: "body2", sx: { mb: 2 }, children: t('Support.RECOVERY_REQUESTS_DESCRIPTION', 'Users who cannot complete MFA can request support. You can review their request and approve or reject it.') }), errorKey && (_jsx(Alert, { severity: "error", sx: SUPPORT_RECOVERY_ALERT_SX, children: errorKey })), _jsxs(Stack, { direction: "row", spacing: 2, sx: { mb: 2 }, children: [_jsx(Button, { variant: statusFilter === 'pending' ? 'contained' : 'outlined', size: "small", onClick: () => setStatusFilter('pending'), children: t('Support.RECOVERY_FILTER_PENDING', 'Open') }), _jsx(Button, { variant: statusFilter === 'approved' ? 'contained' : 'outlined', size: "small", onClick: () => setStatusFilter('approved'), children: t('Support.RECOVERY_FILTER_APPROVED', 'Approved') }), _jsx(Button, { variant: statusFilter === 'rejected' ? 'contained' : 'outlined', size: "small", onClick: () => setStatusFilter('rejected'), children: t('Support.RECOVERY_FILTER_REJECTED', 'Rejected') })] }), requests.length === 0 ? (_jsx(Typography, { variant: "body2", children: t('Support.RECOVERY_REQUESTS_EMPTY', 'No recovery requests for this filter.') })) : (_jsx(TableContainer, { component: Paper, children: _jsxs(Table, { size: "small", children: [_jsx(TableHead, { children: _jsxs(TableRow, { children: [_jsx(TableCell, { children: t('Support.RECOVERY_COL_CREATED', 'Created') }), _jsx(TableCell, { children: t('Support.RECOVERY_USER', 'User') }), _jsx(TableCell, { children: t('Support.RECOVERY_COL_STATUS', 'Status') }), _jsx(TableCell, { children: t('Support.RECOVERY_COL_ACTIONS', 'Actions') })] }) }), _jsx(TableBody, { children: requests.map((req) => (_jsxs(TableRow, { children: [_jsx(TableCell, { children: req.created_at
|
|
167
168
|
? new Date(req.created_at).toLocaleString(i18n === null || i18n === void 0 ? void 0 : i18n.language)
|
|
168
169
|
: '-' }), _jsx(TableCell, { children: req.user_email || req.user }), _jsx(TableCell, { children: t(`Support.RECOVERY_STATUS_${req.status}`, req.status) }), _jsx(TableCell, { children: _jsx(Button, { variant: "contained", size: "small", onClick: () => openDialog(req), disabled: req.status !== 'pending', children: t('Support.RECOVERY_ACTION_REVIEW', 'Review') }) })] }, req.id))) })] }) })), _jsxs(Dialog, { open: dialogOpen, onClose: closeDialog, fullWidth: true, maxWidth: "sm", children: [_jsx(DialogTitle, { children: t('Support.RECOVERY_REVIEW_DIALOG_TITLE', 'Review recovery request') }), _jsxs(DialogContent, { dividers: true, children: [selectedRequest && (_jsxs(Box, { sx: { mb: 2 }, children: [_jsxs(Typography, { variant: "body2", sx: { mb: 1 }, children: [_jsxs("strong", { children: [t('Support.RECOVERY_USER', 'User'), ":"] }), ' ', selectedRequest.user_email || selectedRequest.user] }), _jsxs(Typography, { variant: "body2", sx: { mb: 1 }, children: [_jsxs("strong", { children: [t('Support.RECOVERY_REVIEW_CREATED', 'Requested at'), ":"] }), ' ', selectedRequest.created_at
|
|
169
170
|
? new Date(selectedRequest.created_at).toLocaleString(i18n === null || i18n === void 0 ? void 0 : i18n.language)
|
|
@@ -3,6 +3,7 @@ import React, { useState } from 'react';
|
|
|
3
3
|
import { Box, TextField, Button, Typography, Alert, CircularProgress } from '@mui/material';
|
|
4
4
|
import { sendAdminInvite } from '../auth/authApi';
|
|
5
5
|
import { useTranslation } from 'react-i18next';
|
|
6
|
+
import { resolveErrorText } from '../utils/resolveErrorText';
|
|
6
7
|
export const USER_INVITE_ACTION_SX = {
|
|
7
8
|
minWidth: 120,
|
|
8
9
|
height: 40,
|
|
@@ -10,7 +11,7 @@ export const USER_INVITE_ACTION_SX = {
|
|
|
10
11
|
};
|
|
11
12
|
export const USER_INVITE_ALERT_SX = { mb: 2 };
|
|
12
13
|
export function UserInviteComponent() {
|
|
13
|
-
const { t } = useTranslation();
|
|
14
|
+
const { t, i18n } = useTranslation();
|
|
14
15
|
const [inviteEmail, setInviteEmail] = useState('');
|
|
15
16
|
const [message, setMessage] = useState('');
|
|
16
17
|
const [error, setError] = useState('');
|
|
@@ -30,7 +31,7 @@ export function UserInviteComponent() {
|
|
|
30
31
|
// err.message contains normalized text or code from authApi
|
|
31
32
|
// eslint-disable-next-line no-console
|
|
32
33
|
console.error('Error inviting user:', err);
|
|
33
|
-
setError(
|
|
34
|
+
setError(resolveErrorText(i18n, err, 'Auth.INVITE_FAILED'));
|
|
34
35
|
}
|
|
35
36
|
finally {
|
|
36
37
|
setLoading(false);
|
|
@@ -6,6 +6,7 @@ import CancelIcon from '@mui/icons-material/Cancel';
|
|
|
6
6
|
import DeleteIcon from '@mui/icons-material/Delete';
|
|
7
7
|
import { useTranslation } from 'react-i18next';
|
|
8
8
|
import { fetchUsersList, deleteUser, updateUserRole } from '../auth/authApi';
|
|
9
|
+
import { resolveErrorText } from '../utils/resolveErrorText';
|
|
9
10
|
const DEFAULT_ROLES = ['none', 'student', 'teacher', 'admin'];
|
|
10
11
|
export const USER_LIST_ACTION_SX = { minWidth: 90 };
|
|
11
12
|
export const USER_LIST_ALERT_SX = { mb: 2 };
|
|
@@ -13,7 +14,7 @@ export const USER_LIST_HEADER_CELL_SX = { fontWeight: 600, whiteSpace: 'nowrap'
|
|
|
13
14
|
export const USER_LIST_EMPTY_CELL_SX = { py: 4, color: 'text.secondary' };
|
|
14
15
|
export const USER_LIST_BODY_CELL_SX = { verticalAlign: 'top', py: 1 };
|
|
15
16
|
export function UserListComponent({ roles = DEFAULT_ROLES, currentUser, extraColumns = [], extraRowActions = [], extraContext = null, refreshTrigger = 0, canEditUser, showNewColumn = true, showSuccessfulLoginColumn = true, showRoleColumn = true, onChangeRole = null, showDeleteAction = true, canDeleteUser = null, onDeleteUser = null, }) {
|
|
16
|
-
const { t } = useTranslation();
|
|
17
|
+
const { t, i18n } = useTranslation();
|
|
17
18
|
const [users, setUsers] = useState([]);
|
|
18
19
|
const [loading, setLoading] = useState(true);
|
|
19
20
|
const [error, setError] = useState(null);
|
|
@@ -40,7 +41,7 @@ export function UserListComponent({ roles = DEFAULT_ROLES, currentUser, extraCol
|
|
|
40
41
|
setUsers(list);
|
|
41
42
|
}
|
|
42
43
|
catch (err) {
|
|
43
|
-
setError(err
|
|
44
|
+
setError(resolveErrorText(i18n, err, 'Auth.USER_LIST_FAILED'));
|
|
44
45
|
}
|
|
45
46
|
finally {
|
|
46
47
|
setLoading(false);
|
|
@@ -65,7 +66,7 @@ export function UserListComponent({ roles = DEFAULT_ROLES, currentUser, extraCol
|
|
|
65
66
|
setUsers(prev => prev.filter(u => u.id !== userId));
|
|
66
67
|
}
|
|
67
68
|
catch (err) {
|
|
68
|
-
alert(
|
|
69
|
+
alert(resolveErrorText(i18n, err, 'Auth.USER_DELETE_FAILED'));
|
|
69
70
|
}
|
|
70
71
|
};
|
|
71
72
|
const handleChangeRole = async (userId, newRole) => {
|
|
@@ -87,7 +88,7 @@ export function UserListComponent({ roles = DEFAULT_ROLES, currentUser, extraCol
|
|
|
87
88
|
alert(t('UserList.ROLE_UPDATE_SUCCESS', 'Role updated.'));
|
|
88
89
|
}
|
|
89
90
|
catch (err) {
|
|
90
|
-
alert(
|
|
91
|
+
alert(resolveErrorText(i18n, err, 'Auth.USER_ROLE_UPDATE_FAILED'));
|
|
91
92
|
}
|
|
92
93
|
};
|
|
93
94
|
const defaultCanEdit = (targetUser) => {
|
|
@@ -210,7 +211,7 @@ export function UserListComponent({ roles = DEFAULT_ROLES, currentUser, extraCol
|
|
|
210
211
|
}
|
|
211
212
|
catch (err) {
|
|
212
213
|
// eslint-disable-next-line no-alert
|
|
213
|
-
alert((
|
|
214
|
+
alert(resolveErrorText(i18n, err, 'Common.OPERATION_FAILED'));
|
|
214
215
|
}
|
|
215
216
|
finally {
|
|
216
217
|
setRowActionLoading((prev) => (Object.assign(Object.assign({}, prev), { [actionId]: false })));
|
|
@@ -373,7 +374,7 @@ export function UserListComponent({ roles = DEFAULT_ROLES, currentUser, extraCol
|
|
|
373
374
|
}
|
|
374
375
|
setPage(0);
|
|
375
376
|
};
|
|
376
|
-
return (_jsxs(Box, { children: [_jsx(Typography, { variant: "h6", gutterBottom: true, children: t('UserList.TITLE', 'All Users') }), _jsx(Box, { sx: { mb: 2, maxWidth: 420 }, children: _jsx(TextField, { fullWidth: true, size: "small", label: t('Common.SEARCH', 'Search'), placeholder: t('UserList.SEARCH_PLACEHOLDER', 'Search users...'), value: searchQuery, onChange: (event) => setSearchQuery(event.target.value) }) }), error && _jsx(Alert, { severity: "error", sx: USER_LIST_ALERT_SX, children:
|
|
377
|
+
return (_jsxs(Box, { children: [_jsx(Typography, { variant: "h6", gutterBottom: true, children: t('UserList.TITLE', 'All Users') }), _jsx(Box, { sx: { mb: 2, maxWidth: 420 }, children: _jsx(TextField, { fullWidth: true, size: "small", label: t('Common.SEARCH', 'Search'), placeholder: t('UserList.SEARCH_PLACEHOLDER', 'Search users...'), value: searchQuery, onChange: (event) => setSearchQuery(event.target.value) }) }), error && _jsx(Alert, { severity: "error", sx: USER_LIST_ALERT_SX, children: error }), loading && (_jsx(Box, { sx: { display: 'flex', justifyContent: 'center', p: 3 }, children: _jsx(CircularProgress, {}) })), !loading && (_jsxs(Box, { sx: { width: '100%' }, children: [_jsx(TableContainer, { sx: { border: 1, borderColor: 'divider', borderRadius: 1 }, children: _jsxs(Table, { size: "small", children: [_jsx(TableHead, { sx: { bgcolor: 'action.hover' }, children: _jsx(TableRow, { children: columns.map((col) => (_jsx(TableCell, { align: col.headerAlign || col.align || 'left', sortDirection: sortField === col.field ? sortDir : false, sx: [USER_LIST_HEADER_CELL_SX, { minWidth: col.minWidth }], children: col.sortable !== false ? (_jsx(TableSortLabel, { active: sortField === col.field, direction: sortField === col.field ? sortDir : 'asc', onClick: () => handleSortClick(col.field), children: col.headerName })) : (col.headerName) }, col.field))) }) }), _jsx(TableBody, { children: pagedUsers.length === 0 ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: columns.length, align: "center", sx: USER_LIST_EMPTY_CELL_SX, children: t('UserList.NO_USERS', 'No users found.') }) })) : (pagedUsers.map((row) => (_jsx(TableRow, { hover: true, children: columns.map((col) => (_jsx(TableCell, { align: col.align || 'left', sx: USER_LIST_BODY_CELL_SX, children: col.renderCell(row) }, col.field))) }, row.id)))) })] }) }), _jsx(TablePagination, { component: "div", count: sortedUsers.length, page: page, onPageChange: (_, newPage) => setPage(newPage), rowsPerPage: pageSize, onRowsPerPageChange: (event) => {
|
|
377
378
|
setPageSize(Number(event.target.value));
|
|
378
379
|
setPage(0);
|
|
379
380
|
}, labelRowsPerPage: t('UserList.ROWS_PER_PAGE', 'Rows per page:'), rowsPerPageOptions: [10, 25, 50, 100] })] }))] }));
|