@micha.bigler/ui-core-micha 3.8.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.
Files changed (68) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/WORK_ORDERS.md +1 -0
  3. package/dist/auth/apiClient.js +8 -0
  4. package/dist/components/AccessCodeManager.js +5 -5
  5. package/dist/components/AccessCodeSingleUseToggle.js +3 -2
  6. package/dist/components/AllowedEmailDomainsManager.js +3 -2
  7. package/dist/components/AuthFactorRequirementCard.js +3 -2
  8. package/dist/components/BulkInviteCsvTab.js +3 -2
  9. package/dist/components/MFAComponent.js +9 -8
  10. package/dist/components/MfaLoginComponent.js +7 -6
  11. package/dist/components/PasskeysComponent.js +7 -6
  12. package/dist/components/ProfileComponent.js +4 -3
  13. package/dist/components/QrSignupManager.js +4 -3
  14. package/dist/components/QrSignupValidityManager.js +3 -2
  15. package/dist/components/RegistrationMethodsManager.js +3 -2
  16. package/dist/components/SecurityComponent.js +5 -4
  17. package/dist/components/SupportRecoveryRequestsTab.js +8 -7
  18. package/dist/components/UserInviteComponent.js +3 -2
  19. package/dist/components/UserListComponent.js +7 -6
  20. package/dist/i18n/authTranslations.js +420 -0
  21. package/dist/pages/AccountPage.js +3 -2
  22. package/dist/pages/LoginPage.js +10 -9
  23. package/dist/pages/PasswordChangePage.js +4 -3
  24. package/dist/pages/PasswordInvitePage.js +7 -6
  25. package/dist/pages/PasswordResetRequestPage.js +5 -4
  26. package/dist/pages/SignUpPage.js +7 -6
  27. package/dist/pages/SignupConfirmPage.js +7 -6
  28. package/dist/utils/auth-errors.js +45 -8
  29. package/dist/utils/resolveErrorText.js +24 -0
  30. package/package.json +1 -1
  31. package/src/auth/apiClient.jsx +9 -0
  32. package/src/components/AccessCodeManager.jsx +5 -5
  33. package/src/components/AccessCodeSingleUseToggle.jsx +3 -2
  34. package/src/components/AllowedEmailDomainsManager.jsx +3 -2
  35. package/src/components/AuthFactorRequirementCard.jsx +3 -2
  36. package/src/components/BulkInviteCsvTab.jsx +3 -2
  37. package/src/components/MFAComponent.jsx +9 -8
  38. package/src/components/MfaLoginComponent.jsx +7 -6
  39. package/src/components/PasskeysComponent.jsx +7 -6
  40. package/src/components/ProfileComponent.jsx +4 -3
  41. package/src/components/QrSignupManager.jsx +4 -3
  42. package/src/components/QrSignupValidityManager.jsx +3 -2
  43. package/src/components/RegistrationMethodsManager.jsx +3 -2
  44. package/src/components/SecurityComponent.jsx +5 -4
  45. package/src/components/SupportRecoveryRequestsTab.jsx +9 -8
  46. package/src/components/UserInviteComponent.jsx +3 -2
  47. package/src/components/UserListComponent.jsx +7 -6
  48. package/src/i18n/authTranslations.ts +421 -0
  49. package/src/pages/AccountPage.jsx +3 -4
  50. package/src/pages/LoginPage.jsx +10 -9
  51. package/src/pages/PasswordChangePage.jsx +4 -3
  52. package/src/pages/PasswordInvitePage.jsx +7 -6
  53. package/src/pages/PasswordResetRequestPage.jsx +5 -4
  54. package/src/pages/SignUpPage.jsx +7 -6
  55. package/src/pages/SignupConfirmPage.jsx +7 -6
  56. package/src/utils/auth-errors.js +44 -7
  57. package/src/utils/resolveErrorText.js +28 -0
  58. package/tests/AccountPage.test.jsx +27 -20
  59. package/tests/PasswordInvitePage.test.jsx +28 -21
  60. package/tests/QrSignupManager.registrationContext.test.jsx +5 -1
  61. package/tests/SignUpPage.test.jsx +14 -3
  62. package/tests/UserListComponent.test.jsx +22 -15
  63. package/tests/apiClientLanguage.test.js +33 -0
  64. package/tests/authErrors.test.js +31 -0
  65. package/tests/errorCallSiteGuard.test.js +32 -0
  66. package/tests/loginPageErrorResolution.test.jsx +102 -0
  67. package/tests/resolveErrorText.test.js +72 -0
  68. package/work-orders/UCM-AUTH-10.md +490 -0
@@ -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.code || 'Support.RECOVERY_REQUESTS_LOAD_FAILED');
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.code || 'Auth.USER_LIST_FAILED');
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.code || 'Support.RECOVERY_REQUEST_APPROVE_FAILED');
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.code || 'Support.RECOVERY_REQUEST_REJECT_FAILED');
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.code || 'Auth.USER_SUPPORT_UPDATE_FAILED');
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.code || 'Auth.USER_SUPPORT_UPDATE_FAILED');
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: t(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: t(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
+ 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(t(err.code) || err.message || t('Auth.INVITE_FAILED'));
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.code || 'Auth.USER_LIST_FAILED');
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(t(err.code || 'Auth.USER_DELETE_FAILED'));
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(t(err.code || 'Auth.USER_ROLE_UPDATE_FAILED'));
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((err === null || err === void 0 ? void 0 : err.message) || t('Common.OPERATION_FAILED', 'Operation failed.'));
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: t(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
+ 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] })] }))] }));
@@ -2084,6 +2084,426 @@ export const authTranslations = {
2084
2084
  "en": "{{count}} successful",
2085
2085
  "sw": "{{count}} zimefaulu"
2086
2086
  },
2087
+ "Auth.field.email.invalid": {
2088
+ "de": "Bitte geben Sie eine gültige E-Mail-Adresse ein.",
2089
+ "de_informal": "Bitte gib eine gültige E-Mail-Adresse ein.",
2090
+ "fr": "Saisissez une adresse e-mail valide.",
2091
+ "en": "Enter a valid email address.",
2092
+ "sw": "Weka anwani sahihi ya barua pepe."
2093
+ },
2094
+ "Auth.field.email.required": {
2095
+ "de": "Bitte geben Sie eine E-Mail-Adresse ein.",
2096
+ "de_informal": "Bitte gib eine E-Mail-Adresse ein.",
2097
+ "fr": "Saisissez une adresse e-mail.",
2098
+ "en": "Enter an email address.",
2099
+ "sw": "Weka anwani ya barua pepe."
2100
+ },
2101
+ "Auth.field.password.invalid": {
2102
+ "de": "Bitte geben Sie ein gültiges Passwort ein.",
2103
+ "de_informal": "Bitte gib ein gültiges Passwort ein.",
2104
+ "fr": "Saisissez un mot de passe valide.",
2105
+ "en": "Enter a valid password.",
2106
+ "sw": "Weka nenosiri sahihi."
2107
+ },
2108
+ "Auth.field.password.required": {
2109
+ "de": "Bitte geben Sie ein Passwort ein.",
2110
+ "de_informal": "Bitte gib ein Passwort ein.",
2111
+ "fr": "Saisissez un mot de passe.",
2112
+ "en": "Enter a password.",
2113
+ "sw": "Weka nenosiri."
2114
+ },
2115
+ "Auth.field.new_password.invalid": {
2116
+ "de": "Bitte geben Sie ein gültiges neues Passwort ein.",
2117
+ "de_informal": "Bitte gib ein gültiges neues Passwort ein.",
2118
+ "fr": "Saisissez un nouveau mot de passe valide.",
2119
+ "en": "Enter a valid new password.",
2120
+ "sw": "Weka nenosiri jipya sahihi."
2121
+ },
2122
+ "Auth.field.new_password.required": {
2123
+ "de": "Bitte geben Sie ein neues Passwort ein.",
2124
+ "de_informal": "Bitte gib ein neues Passwort ein.",
2125
+ "fr": "Saisissez un nouveau mot de passe.",
2126
+ "en": "Enter a new password.",
2127
+ "sw": "Weka nenosiri jipya."
2128
+ },
2129
+ "Auth.field.current_password.invalid": {
2130
+ "de": "Bitte geben Sie Ihr aktuelles Passwort korrekt ein.",
2131
+ "de_informal": "Bitte gib dein aktuelles Passwort korrekt ein.",
2132
+ "fr": "Saisissez correctement votre mot de passe actuel.",
2133
+ "en": "Enter your current password correctly.",
2134
+ "sw": "Weka nenosiri lako la sasa kwa usahihi."
2135
+ },
2136
+ "Auth.field.current_password.required": {
2137
+ "de": "Bitte geben Sie Ihr aktuelles Passwort ein.",
2138
+ "de_informal": "Bitte gib dein aktuelles Passwort ein.",
2139
+ "fr": "Saisissez votre mot de passe actuel.",
2140
+ "en": "Enter your current password.",
2141
+ "sw": "Weka nenosiri lako la sasa."
2142
+ },
2143
+ "Auth.field.key.invalid": {
2144
+ "de": "Bitte geben Sie einen gültigen Schlüssel ein.",
2145
+ "de_informal": "Bitte gib einen gültigen Schlüssel ein.",
2146
+ "fr": "Saisissez une clé valide.",
2147
+ "en": "Enter a valid key.",
2148
+ "sw": "Weka ufunguo sahihi."
2149
+ },
2150
+ "Auth.field.key.required": {
2151
+ "de": "Bitte geben Sie einen Schlüssel ein.",
2152
+ "de_informal": "Bitte gib einen Schlüssel ein.",
2153
+ "fr": "Saisissez une clé.",
2154
+ "en": "Enter a key.",
2155
+ "sw": "Weka ufunguo."
2156
+ },
2157
+ "Auth.field.code.invalid": {
2158
+ "de": "Bitte geben Sie einen gültigen Code ein.",
2159
+ "de_informal": "Bitte gib einen gültigen Code ein.",
2160
+ "fr": "Saisissez un code valide.",
2161
+ "en": "Enter a valid code.",
2162
+ "sw": "Weka msimbo sahihi."
2163
+ },
2164
+ "Auth.field.code.required": {
2165
+ "de": "Bitte geben Sie einen Code ein.",
2166
+ "de_informal": "Bitte gib einen Code ein.",
2167
+ "fr": "Saisissez un code.",
2168
+ "en": "Enter a code.",
2169
+ "sw": "Weka msimbo."
2170
+ },
2171
+ "Auth.field.credential.invalid": {
2172
+ "de": "Bitte verwenden Sie eine gültige Sicherheitsmethode.",
2173
+ "de_informal": "Bitte verwende eine gültige Sicherheitsmethode.",
2174
+ "fr": "Utilisez une méthode de sécurité valide.",
2175
+ "en": "Use a valid security method.",
2176
+ "sw": "Tumia njia sahihi ya usalama."
2177
+ },
2178
+ "Auth.field.credential.required": {
2179
+ "de": "Bitte verwenden Sie eine Sicherheitsmethode.",
2180
+ "de_informal": "Bitte verwende eine Sicherheitsmethode.",
2181
+ "fr": "Utilisez une méthode de sécurité.",
2182
+ "en": "Use a security method.",
2183
+ "sw": "Tumia njia ya usalama."
2184
+ },
2185
+ "Auth.field.name.invalid": {
2186
+ "de": "Bitte geben Sie einen gültigen Namen ein.",
2187
+ "de_informal": "Bitte gib einen gültigen Namen ein.",
2188
+ "fr": "Saisissez un nom valide.",
2189
+ "en": "Enter a valid name.",
2190
+ "sw": "Weka jina sahihi."
2191
+ },
2192
+ "Auth.field.name.required": {
2193
+ "de": "Bitte geben Sie einen Namen ein.",
2194
+ "de_informal": "Bitte gib einen Namen ein.",
2195
+ "fr": "Saisissez un nom.",
2196
+ "en": "Enter a name.",
2197
+ "sw": "Weka jina."
2198
+ },
2199
+ "Auth.field.mode.invalid": {
2200
+ "de": "Bitte wählen Sie eine gültige Anmeldemethode.",
2201
+ "de_informal": "Bitte wähle eine gültige Anmeldemethode.",
2202
+ "fr": "Sélectionnez un mode d'inscription valide.",
2203
+ "en": "Choose a valid sign-up method.",
2204
+ "sw": "Chagua njia sahihi ya kujisajili."
2205
+ },
2206
+ "Auth.field.mode.required": {
2207
+ "de": "Bitte wählen Sie eine Anmeldemethode.",
2208
+ "de_informal": "Bitte wähle eine Anmeldemethode.",
2209
+ "fr": "Sélectionnez un mode d'inscription.",
2210
+ "en": "Choose a sign-up method.",
2211
+ "sw": "Chagua njia ya kujisajili."
2212
+ },
2213
+ "Auth.field.access_code.invalid": {
2214
+ "de": "Bitte geben Sie einen gültigen Zugangscode ein.",
2215
+ "de_informal": "Bitte gib einen gültigen Zugangscode ein.",
2216
+ "fr": "Saisissez un code d'accès valide.",
2217
+ "en": "Enter a valid access code.",
2218
+ "sw": "Weka msimbo sahihi wa ufikiaji."
2219
+ },
2220
+ "Auth.field.access_code.required": {
2221
+ "de": "Bitte geben Sie einen Zugangscode ein.",
2222
+ "de_informal": "Bitte gib einen Zugangscode ein.",
2223
+ "fr": "Saisissez un code d'accès.",
2224
+ "en": "Enter an access code.",
2225
+ "sw": "Weka msimbo wa ufikiaji."
2226
+ },
2227
+ "Auth.field.registration_context_token.invalid": {
2228
+ "de": "Bitte verwenden Sie einen gültigen Registrierungslink.",
2229
+ "de_informal": "Bitte verwende einen gültigen Registrierungslink.",
2230
+ "fr": "Utilisez un lien d'inscription valide.",
2231
+ "en": "Use a valid registration link.",
2232
+ "sw": "Tumia kiungo sahihi cha usajili."
2233
+ },
2234
+ "Auth.field.registration_context_token.required": {
2235
+ "de": "Bitte verwenden Sie einen Registrierungslink.",
2236
+ "de_informal": "Bitte verwende einen Registrierungslink.",
2237
+ "fr": "Utilisez un lien d'inscription.",
2238
+ "en": "Use a registration link.",
2239
+ "sw": "Tumia kiungo cha usajili."
2240
+ },
2241
+ "Auth.field.registration_context.invalid": {
2242
+ "de": "Die Registrierungsdaten sind ungültig.",
2243
+ "de_informal": "Die Registrierungsdaten sind ungültig.",
2244
+ "fr": "Les données d'inscription sont invalides.",
2245
+ "en": "The registration data is invalid.",
2246
+ "sw": "Data ya usajili si sahihi."
2247
+ },
2248
+ "Auth.field.registration_context.required": {
2249
+ "de": "Bitte geben Sie die Registrierungsdaten an.",
2250
+ "de_informal": "Bitte gib die Registrierungsdaten an.",
2251
+ "fr": "Indiquez les données d'inscription.",
2252
+ "en": "Provide the registration data.",
2253
+ "sw": "Weka data ya usajili."
2254
+ },
2255
+ "Auth.field.turnstile_token.invalid": {
2256
+ "de": "Bitte bestätigen Sie die Sicherheitsprüfung erneut.",
2257
+ "de_informal": "Bitte bestätige die Sicherheitsprüfung erneut.",
2258
+ "fr": "Recommencez la vérification de sécurité.",
2259
+ "en": "Complete the security check again.",
2260
+ "sw": "Kamilisha ukaguzi wa usalama tena."
2261
+ },
2262
+ "Auth.field.turnstile_token.required": {
2263
+ "de": "Bitte bestätigen Sie die Sicherheitsprüfung.",
2264
+ "de_informal": "Bitte bestätige die Sicherheitsprüfung.",
2265
+ "fr": "Confirmez la vérification de sécurité.",
2266
+ "en": "Complete the security check.",
2267
+ "sw": "Kamilisha ukaguzi wa usalama."
2268
+ },
2269
+ "Auth.field.token.invalid": {
2270
+ "de": "Bitte verwenden Sie einen gültigen Bestätigungstoken.",
2271
+ "de_informal": "Bitte verwende einen gültigen Bestätigungstoken.",
2272
+ "fr": "Utilisez un jeton de confirmation valide.",
2273
+ "en": "Use a valid confirmation token.",
2274
+ "sw": "Tumia tokeni sahihi ya uthibitishaji."
2275
+ },
2276
+ "Auth.field.token.required": {
2277
+ "de": "Bitte geben Sie den Bestätigungstoken an.",
2278
+ "de_informal": "Bitte gib den Bestätigungstoken an.",
2279
+ "fr": "Indiquez le jeton de confirmation.",
2280
+ "en": "Provide the confirmation token.",
2281
+ "sw": "Weka tokeni ya uthibitishaji."
2282
+ },
2283
+ "Auth.field.first_name.invalid": {
2284
+ "de": "Bitte geben Sie einen gültigen Vornamen ein.",
2285
+ "de_informal": "Bitte gib einen gültigen Vornamen ein.",
2286
+ "fr": "Saisissez un prénom valide.",
2287
+ "en": "Enter a valid first name.",
2288
+ "sw": "Weka jina la kwanza sahihi."
2289
+ },
2290
+ "Auth.field.first_name.required": {
2291
+ "de": "Bitte geben Sie Ihren Vornamen ein.",
2292
+ "de_informal": "Bitte gib deinen Vornamen ein.",
2293
+ "fr": "Saisissez votre prénom.",
2294
+ "en": "Enter your first name.",
2295
+ "sw": "Weka jina lako la kwanza."
2296
+ },
2297
+ "Auth.field.last_name.invalid": {
2298
+ "de": "Bitte geben Sie einen gültigen Nachnamen ein.",
2299
+ "de_informal": "Bitte gib einen gültigen Nachnamen ein.",
2300
+ "fr": "Saisissez un nom de famille valide.",
2301
+ "en": "Enter a valid last name.",
2302
+ "sw": "Weka jina la mwisho sahihi."
2303
+ },
2304
+ "Auth.field.last_name.required": {
2305
+ "de": "Bitte geben Sie Ihren Nachnamen ein.",
2306
+ "de_informal": "Bitte gib deinen Nachnamen ein.",
2307
+ "fr": "Saisissez votre nom de famille.",
2308
+ "en": "Enter your last name.",
2309
+ "sw": "Weka jina lako la mwisho."
2310
+ },
2311
+ "Auth.field.accepted_privacy_statement.invalid": {
2312
+ "de": "Bitte bestätigen Sie die Datenschutzerklärung.",
2313
+ "de_informal": "Bitte bestätige die Datenschutzerklärung.",
2314
+ "fr": "Confirmez la déclaration de confidentialité.",
2315
+ "en": "Confirm the privacy statement.",
2316
+ "sw": "Thibitisha taarifa ya faragha."
2317
+ },
2318
+ "Auth.field.accepted_privacy_statement.required": {
2319
+ "de": "Bitte bestätigen Sie die Datenschutzerklärung.",
2320
+ "de_informal": "Bitte bestätige die Datenschutzerklärung.",
2321
+ "fr": "Confirmez la déclaration de confidentialité.",
2322
+ "en": "Confirm the privacy statement.",
2323
+ "sw": "Thibitisha taarifa ya faragha."
2324
+ },
2325
+ "Auth.field.accepted_convenience_cookies.invalid": {
2326
+ "de": "Die Cookie-Einstellung ist ungültig.",
2327
+ "de_informal": "Die Cookie-Einstellung ist ungültig.",
2328
+ "fr": "Le choix des cookies est invalide.",
2329
+ "en": "The cookie preference is invalid.",
2330
+ "sw": "Chaguo la vidakuzi si sahihi."
2331
+ },
2332
+ "Auth.field.accepted_convenience_cookies.required": {
2333
+ "de": "Bitte geben Sie Ihre Cookie-Einstellung an.",
2334
+ "de_informal": "Bitte gib deine Cookie-Einstellung an.",
2335
+ "fr": "Indiquez votre choix de cookies.",
2336
+ "en": "Provide your cookie preference.",
2337
+ "sw": "Weka chaguo lako la vidakuzi."
2338
+ },
2339
+ "Auth.field.expires_minutes.invalid": {
2340
+ "de": "Bitte geben Sie eine gültige Ablaufzeit ein.",
2341
+ "de_informal": "Bitte gib eine gültige Ablaufzeit ein.",
2342
+ "fr": "Saisissez une durée de validité valide.",
2343
+ "en": "Enter a valid expiry period.",
2344
+ "sw": "Weka muda sahihi wa kuisha."
2345
+ },
2346
+ "Auth.field.expires_minutes.required": {
2347
+ "de": "Bitte geben Sie eine Ablaufzeit an.",
2348
+ "de_informal": "Bitte gib eine Ablaufzeit an.",
2349
+ "fr": "Indiquez une durée de validité.",
2350
+ "en": "Provide an expiry period.",
2351
+ "sw": "Weka muda wa kuisha."
2352
+ },
2353
+ "Auth.field.max_redemptions.invalid": {
2354
+ "de": "Bitte geben Sie eine gültige Anzahl von Einlösungen ein.",
2355
+ "de_informal": "Bitte gib eine gültige Anzahl von Einlösungen ein.",
2356
+ "fr": "Saisissez un nombre de validations valide.",
2357
+ "en": "Enter a valid redemption count.",
2358
+ "sw": "Weka idadi sahihi ya matumizi."
2359
+ },
2360
+ "Auth.field.max_redemptions.required": {
2361
+ "de": "Bitte geben Sie die maximale Anzahl von Einlösungen an.",
2362
+ "de_informal": "Bitte gib die maximale Anzahl von Einlösungen an.",
2363
+ "fr": "Indiquez le nombre maximal de validations.",
2364
+ "en": "Provide the maximum redemption count.",
2365
+ "sw": "Weka idadi ya juu ya matumizi."
2366
+ },
2367
+ "Auth.field.signup_qr_expiry_days.invalid": {
2368
+ "de": "Bitte geben Sie eine gültige QR-Gültigkeitsdauer ein.",
2369
+ "de_informal": "Bitte gib eine gültige QR-Gültigkeitsdauer ein.",
2370
+ "fr": "Saisissez une durée de validité QR valide.",
2371
+ "en": "Enter a valid QR validity period.",
2372
+ "sw": "Weka muda sahihi wa QR."
2373
+ },
2374
+ "Auth.field.signup_qr_expiry_days.required": {
2375
+ "de": "Bitte geben Sie die QR-Gültigkeitsdauer an.",
2376
+ "de_informal": "Bitte gib die QR-Gültigkeitsdauer an.",
2377
+ "fr": "Indiquez la durée de validité QR.",
2378
+ "en": "Provide the QR validity period.",
2379
+ "sw": "Weka muda wa QR."
2380
+ },
2381
+ "Auth.field.allowed_email_domains.invalid": {
2382
+ "de": "Bitte geben Sie gültige E-Mail-Domains ein.",
2383
+ "de_informal": "Bitte gib gültige E-Mail-Domains ein.",
2384
+ "fr": "Saisissez des domaines e-mail valides.",
2385
+ "en": "Enter valid email domains.",
2386
+ "sw": "Weka vikoa sahihi vya barua pepe."
2387
+ },
2388
+ "Auth.field.allowed_email_domains.required": {
2389
+ "de": "Bitte geben Sie die erlaubten E-Mail-Domains an.",
2390
+ "de_informal": "Bitte gib die erlaubten E-Mail-Domains an.",
2391
+ "fr": "Indiquez les domaines e-mail autorisés.",
2392
+ "en": "Provide the allowed email domains.",
2393
+ "sw": "Weka vikoa vinavyoruhusiwa vya barua pepe."
2394
+ },
2395
+ "Auth.field.required_auth_factor_count.invalid": {
2396
+ "de": "Bitte wählen Sie eine gültige Anzahl von Sicherheitsfaktoren.",
2397
+ "de_informal": "Bitte wähle eine gültige Anzahl von Sicherheitsfaktoren.",
2398
+ "fr": "Sélectionnez un nombre de facteurs de sécurité valide.",
2399
+ "en": "Choose a valid number of security factors.",
2400
+ "sw": "Chagua idadi sahihi ya vipengele vya usalama."
2401
+ },
2402
+ "Auth.field.required_auth_factor_count.required": {
2403
+ "de": "Bitte wählen Sie die Anzahl der Sicherheitsfaktoren.",
2404
+ "de_informal": "Bitte wähle die Anzahl der Sicherheitsfaktoren.",
2405
+ "fr": "Sélectionnez le nombre de facteurs de sécurité.",
2406
+ "en": "Choose the number of security factors.",
2407
+ "sw": "Chagua idadi ya vipengele vya usalama."
2408
+ },
2409
+ "Auth.field.allow_admin_invite.invalid": {
2410
+ "de": "Diese Einstellung für Admin-Einladungen ist ungültig.",
2411
+ "de_informal": "Diese Einstellung für Admin-Einladungen ist ungültig.",
2412
+ "fr": "Ce paramètre d'invitation administrateur est invalide.",
2413
+ "en": "This admin-invite setting is invalid.",
2414
+ "sw": "Mpangilio huu wa mialiko ya msimamizi si sahihi."
2415
+ },
2416
+ "Auth.field.allow_admin_invite.required": {
2417
+ "de": "Bitte geben Sie die Einstellung für Admin-Einladungen an.",
2418
+ "de_informal": "Bitte gib die Einstellung für Admin-Einladungen an.",
2419
+ "fr": "Indiquez le paramètre d'invitation administrateur.",
2420
+ "en": "Provide the admin-invite setting.",
2421
+ "sw": "Toa mpangilio wa mialiko ya msimamizi."
2422
+ },
2423
+ "Auth.field.allow_self_signup_access_code.invalid": {
2424
+ "de": "Diese Einstellung für die Registrierung per Zugangscode ist ungültig.",
2425
+ "de_informal": "Diese Einstellung für die Registrierung per Zugangscode ist ungültig.",
2426
+ "fr": "Ce paramètre d'inscription par code d'accès est invalide.",
2427
+ "en": "This access-code sign-up setting is invalid.",
2428
+ "sw": "Mpangilio huu wa kujisajili kwa msimbo wa ufikiaji si sahihi."
2429
+ },
2430
+ "Auth.field.allow_self_signup_access_code.required": {
2431
+ "de": "Bitte geben Sie die Einstellung für die Registrierung per Zugangscode an.",
2432
+ "de_informal": "Bitte gib die Einstellung für die Registrierung per Zugangscode an.",
2433
+ "fr": "Indiquez le paramètre d'inscription par code d'accès.",
2434
+ "en": "Provide the access-code sign-up setting.",
2435
+ "sw": "Toa mpangilio wa kujisajili kwa msimbo wa ufikiaji."
2436
+ },
2437
+ "Auth.field.allow_self_signup_open.invalid": {
2438
+ "de": "Diese Einstellung für die offene Registrierung ist ungültig.",
2439
+ "de_informal": "Diese Einstellung für die offene Registrierung ist ungültig.",
2440
+ "fr": "Ce paramètre d'inscription ouverte est invalide.",
2441
+ "en": "This open sign-up setting is invalid.",
2442
+ "sw": "Mpangilio huu wa kujisajili wazi si sahihi."
2443
+ },
2444
+ "Auth.field.allow_self_signup_open.required": {
2445
+ "de": "Bitte geben Sie die Einstellung für die offene Registrierung an.",
2446
+ "de_informal": "Bitte gib die Einstellung für die offene Registrierung an.",
2447
+ "fr": "Indiquez le paramètre d'inscription ouverte.",
2448
+ "en": "Provide the open sign-up setting.",
2449
+ "sw": "Toa mpangilio wa kujisajili wazi."
2450
+ },
2451
+ "Auth.field.allow_self_signup_email_domain.invalid": {
2452
+ "de": "Diese Einstellung für die Registrierung per E-Mail-Domain ist ungültig.",
2453
+ "de_informal": "Diese Einstellung für die Registrierung per E-Mail-Domain ist ungültig.",
2454
+ "fr": "Ce paramètre d'inscription par domaine e-mail est invalide.",
2455
+ "en": "This email-domain sign-up setting is invalid.",
2456
+ "sw": "Mpangilio huu wa kujisajili kwa domeni ya barua pepe si sahihi."
2457
+ },
2458
+ "Auth.field.allow_self_signup_email_domain.required": {
2459
+ "de": "Bitte geben Sie die Einstellung für die Registrierung per E-Mail-Domain an.",
2460
+ "de_informal": "Bitte gib die Einstellung für die Registrierung per E-Mail-Domain an.",
2461
+ "fr": "Indiquez le paramètre d'inscription par domaine e-mail.",
2462
+ "en": "Provide the email-domain sign-up setting.",
2463
+ "sw": "Toa mpangilio wa kujisajili kwa domeni ya barua pepe."
2464
+ },
2465
+ "Auth.field.allow_self_signup_qr.invalid": {
2466
+ "de": "Diese Einstellung für die QR-Registrierung ist ungültig.",
2467
+ "de_informal": "Diese Einstellung für die QR-Registrierung ist ungültig.",
2468
+ "fr": "Ce paramètre d'inscription par QR est invalide.",
2469
+ "en": "This QR sign-up setting is invalid.",
2470
+ "sw": "Mpangilio huu wa kujisajili kwa QR si sahihi."
2471
+ },
2472
+ "Auth.field.allow_self_signup_qr.required": {
2473
+ "de": "Bitte geben Sie die Einstellung für die QR-Registrierung an.",
2474
+ "de_informal": "Bitte gib die Einstellung für die QR-Registrierung an.",
2475
+ "fr": "Indiquez le paramètre d'inscription par QR.",
2476
+ "en": "Provide the QR sign-up setting.",
2477
+ "sw": "Toa mpangilio wa kujisajili kwa QR."
2478
+ },
2479
+ "Auth.field.message.invalid": {
2480
+ "de": "Bitte geben Sie eine gültige Nachricht ein.",
2481
+ "de_informal": "Bitte gib eine gültige Nachricht ein.",
2482
+ "fr": "Saisissez un message valide.",
2483
+ "en": "Enter a valid message.",
2484
+ "sw": "Weka ujumbe sahihi."
2485
+ },
2486
+ "Auth.field.message.required": {
2487
+ "de": "Bitte geben Sie eine Nachricht ein.",
2488
+ "de_informal": "Bitte gib eine Nachricht ein.",
2489
+ "fr": "Saisissez un message.",
2490
+ "en": "Enter a message.",
2491
+ "sw": "Weka ujumbe."
2492
+ },
2493
+ "Auth.field.support_note.invalid": {
2494
+ "de": "Bitte geben Sie eine gültige Supportnotiz ein.",
2495
+ "de_informal": "Bitte gib eine gültige Supportnotiz ein.",
2496
+ "fr": "Saisissez une note de support valide.",
2497
+ "en": "Enter a valid support note.",
2498
+ "sw": "Weka dokezo sahihi la usaidizi."
2499
+ },
2500
+ "Auth.field.support_note.required": {
2501
+ "de": "Bitte geben Sie eine Supportnotiz ein.",
2502
+ "de_informal": "Bitte gib eine Supportnotiz ein.",
2503
+ "fr": "Saisissez une note de support.",
2504
+ "en": "Enter a support note.",
2505
+ "sw": "Weka dokezo la usaidizi."
2506
+ },
2087
2507
  // Raw backend auth error codes (allauth-headless + Django password validators).
2088
2508
  // Keyed by the exact code string so the existing `t(err.code)` call sites resolve
2089
2509
  // as a drop-in, without any component change. See AUTH-2.
@@ -5,6 +5,7 @@ import { useSearchParams } from 'react-router-dom';
5
5
  import { Box, Typography, Alert, CircularProgress, Paper, Stack, useMediaQuery, } from '@mui/material';
6
6
  import { useTheme } from '@mui/material/styles';
7
7
  import { useTranslation } from 'react-i18next';
8
+ import { resolveErrorText } from '../utils/resolveErrorText';
8
9
  // Internal context
9
10
  import { AuthContext } from '../auth/AuthContext';
10
11
  export const ACCOUNT_PAGE_SECTION_PAPER_SX = { p: 2.5 };
@@ -27,7 +28,7 @@ import { BulkInviteCsvTab } from '../components/BulkInviteCsvTab';
27
28
  import { fetchAuthPolicy, updateUserProfile } from '../auth/authApi'; // Ggf. Pfad anpassen
28
29
  export function AccountPage({ userListExtraColumns = [], userListExtraRowActions = [], userListExtraContext = null, userListRefreshTrigger = 0, userListCanEditUser = null, userListShowNewColumn = true, userListShowSuccessfulLoginColumn = true, userListShowRoleColumn = true, userListOnChangeRole = null, userListShowDeleteAction = true, userListCanDeleteUser = null, userListOnDeleteUser = null, showBulkInviteCsvTab = false, bulkInviteCsvProps = {}, extraTabs = [], }) {
29
30
  var _a;
30
- const { t } = useTranslation();
31
+ const { t, i18n } = useTranslation();
31
32
  const { user, login, loading } = useContext(AuthContext);
32
33
  const [searchParams, setSearchParams] = useSearchParams();
33
34
  const [authPolicy, setAuthPolicy] = useState(null);
@@ -80,7 +81,7 @@ export function AccountPage({ userListExtraColumns = [], userListExtraRowActions
80
81
  if (!active)
81
82
  return;
82
83
  setAuthPolicy(null);
83
- setAuthPolicyError(t((err === null || err === void 0 ? void 0 : err.code) || 'Auth.AUTH_POLICY_FETCH_FAILED', 'Could not load authentication policy.'));
84
+ setAuthPolicyError(resolveErrorText(i18n, err, 'Auth.AUTH_POLICY_FETCH_FAILED'));
84
85
  }
85
86
  };
86
87
  loadPolicy();