@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.
Files changed (76) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/DESIGN.md +8 -0
  3. package/WORK_ORDERS.md +2 -0
  4. package/dist/auth/apiClient.js +8 -0
  5. package/dist/components/AccessCodeManager.js +5 -5
  6. package/dist/components/AccessCodeSingleUseToggle.js +3 -2
  7. package/dist/components/AllowedEmailDomainsManager.js +3 -2
  8. package/dist/components/AuthFactorRequirementCard.js +3 -2
  9. package/dist/components/BulkInviteCsvTab.js +3 -2
  10. package/dist/components/MFAComponent.js +9 -8
  11. package/dist/components/MfaLoginComponent.js +7 -6
  12. package/dist/components/PasskeysComponent.js +7 -6
  13. package/dist/components/ProfileComponent.js +4 -3
  14. package/dist/components/QrSignupManager.js +4 -3
  15. package/dist/components/QrSignupValidityManager.js +3 -2
  16. package/dist/components/RegistrationMethodsManager.js +3 -2
  17. package/dist/components/SecurityComponent.js +5 -4
  18. package/dist/components/SupportRecoveryRequestsTab.js +8 -7
  19. package/dist/components/UserInviteComponent.js +3 -2
  20. package/dist/components/UserListComponent.js +7 -6
  21. package/dist/i18n/authTranslations.js +420 -0
  22. package/dist/pages/AccountPage.js +3 -2
  23. package/dist/pages/LoginPage.js +10 -9
  24. package/dist/pages/PasswordChangePage.js +4 -3
  25. package/dist/pages/PasswordInvitePage.js +7 -6
  26. package/dist/pages/PasswordResetRequestPage.js +5 -4
  27. package/dist/pages/SignUpPage.js +7 -6
  28. package/dist/pages/SignupConfirmPage.js +7 -6
  29. package/dist/theme/createAppTheme.js +62 -9
  30. package/dist/theme/tokens.js +1 -1
  31. package/dist/utils/auth-errors.js +45 -8
  32. package/dist/utils/resolveErrorText.js +24 -0
  33. package/package.json +1 -1
  34. package/src/auth/apiClient.jsx +9 -0
  35. package/src/components/AccessCodeManager.jsx +5 -5
  36. package/src/components/AccessCodeSingleUseToggle.jsx +3 -2
  37. package/src/components/AllowedEmailDomainsManager.jsx +3 -2
  38. package/src/components/AuthFactorRequirementCard.jsx +3 -2
  39. package/src/components/BulkInviteCsvTab.jsx +3 -2
  40. package/src/components/MFAComponent.jsx +9 -8
  41. package/src/components/MfaLoginComponent.jsx +7 -6
  42. package/src/components/PasskeysComponent.jsx +7 -6
  43. package/src/components/ProfileComponent.jsx +4 -3
  44. package/src/components/QrSignupManager.jsx +4 -3
  45. package/src/components/QrSignupValidityManager.jsx +3 -2
  46. package/src/components/RegistrationMethodsManager.jsx +3 -2
  47. package/src/components/SecurityComponent.jsx +5 -4
  48. package/src/components/SupportRecoveryRequestsTab.jsx +9 -8
  49. package/src/components/UserInviteComponent.jsx +3 -2
  50. package/src/components/UserListComponent.jsx +7 -6
  51. package/src/i18n/authTranslations.ts +421 -0
  52. package/src/pages/AccountPage.jsx +3 -4
  53. package/src/pages/LoginPage.jsx +10 -9
  54. package/src/pages/PasswordChangePage.jsx +4 -3
  55. package/src/pages/PasswordInvitePage.jsx +7 -6
  56. package/src/pages/PasswordResetRequestPage.jsx +5 -4
  57. package/src/pages/SignUpPage.jsx +7 -6
  58. package/src/pages/SignupConfirmPage.jsx +7 -6
  59. package/src/theme/createAppTheme.js +68 -4
  60. package/src/theme/tokens.js +1 -1
  61. package/src/utils/auth-errors.js +44 -7
  62. package/src/utils/resolveErrorText.js +28 -0
  63. package/tests/AccountPage.test.jsx +27 -20
  64. package/tests/PasswordInvitePage.test.jsx +28 -21
  65. package/tests/QrSignupManager.registrationContext.test.jsx +5 -1
  66. package/tests/SignUpPage.test.jsx +14 -3
  67. package/tests/UserListComponent.test.jsx +22 -15
  68. package/tests/apiClientLanguage.test.js +33 -0
  69. package/tests/authErrors.test.js +31 -0
  70. package/tests/createAppThemePageContrast.test.js +108 -0
  71. package/tests/errorCallSiteGuard.test.js +32 -0
  72. package/tests/loginPageErrorResolution.test.jsx +102 -0
  73. package/tests/resolveErrorText.test.js +72 -0
  74. package/work-orders/UCM-AUTH-10.md +490 -0
  75. package/work-orders/UCM-THEME-14.md +3 -1
  76. package/work-orders/UCM-THEME-15.md +356 -0
@@ -15,13 +15,14 @@ import {
15
15
  import { useTranslation } from 'react-i18next';
16
16
  import { authenticateWithMFA, fetchCurrentUser, requestMfaSupportHelp } from '../auth/authApi';
17
17
  import { loginWithPasskey } from '../utils/authService';
18
+ import { resolveErrorText } from '../utils/resolveErrorText';
18
19
 
19
20
  export const MFA_LOGIN_ALERT_SX = { mb: 2 };
20
21
  export const MFA_LOGIN_DIVIDER_SX = { my: 2 };
21
22
  export const MFA_LOGIN_TEXT_FIELD_SX = { mb: 2 };
22
23
 
23
24
  export function MfaLoginComponent({ availableTypes, identifier, onSuccess, onCancel }) {
24
- const { t } = useTranslation();
25
+ const { t, i18n } = useTranslation();
25
26
  const [code, setCode] = useState('');
26
27
  const [submitting, setSubmitting] = useState(false);
27
28
  const [errorKey, setErrorKey] = useState(null);
@@ -54,7 +55,7 @@ export function MfaLoginComponent({ availableTypes, identifier, onSuccess, onCan
54
55
  method: isRecovery ? 'recovery_code' : 'totp',
55
56
  });
56
57
  } catch (err) {
57
- setErrorKey(err.code || 'Auth.MFA_AUTHENTICATE_FAILED');
58
+ setErrorKey(resolveErrorText(i18n, err, 'Auth.MFA_AUTHENTICATE_FAILED'));
58
59
  } finally {
59
60
  setSubmitting(false);
60
61
  }
@@ -73,11 +74,11 @@ export function MfaLoginComponent({ availableTypes, identifier, onSuccess, onCan
73
74
  // Detailed error handling
74
75
  if (err.code === 'Auth.PASSKEY_CANCELLED') {
75
76
  // User cancelled - show specific key or just ignore
76
- setErrorKey('Auth.PASSKEY_CANCELLED');
77
+ setErrorKey(t('Auth.PASSKEY_CANCELLED'));
77
78
  } else {
78
79
  // eslint-disable-next-line no-console
79
80
  console.error(err);
80
- setErrorKey('Auth.PASSKEY_FAILED');
81
+ setErrorKey(t('Auth.PASSKEY_FAILED'));
81
82
  }
82
83
  } finally {
83
84
  setSubmitting(false);
@@ -101,7 +102,7 @@ export function MfaLoginComponent({ availableTypes, identifier, onSuccess, onCan
101
102
  setHelpDialogOpen(false);
102
103
  setHelpMessage('');
103
104
  } catch (err) {
104
- setErrorKey(err.code || 'Auth.MFA_HELP_REQUEST_FAILED');
105
+ setErrorKey(resolveErrorText(i18n, err, 'Auth.MFA_HELP_REQUEST_FAILED'));
105
106
  } finally {
106
107
  setSubmitting(false);
107
108
  }
@@ -118,7 +119,7 @@ export function MfaLoginComponent({ availableTypes, identifier, onSuccess, onCan
118
119
 
119
120
  {errorKey && (
120
121
  <Alert severity="error" sx={MFA_LOGIN_ALERT_SX}>
121
- {t(errorKey)}
122
+ {errorKey}
122
123
  </Alert>
123
124
  )}
124
125
 
@@ -21,12 +21,13 @@ import { useTranslation } from 'react-i18next';
21
21
  import { fetchPasskeys, deletePasskey } from '../auth/authApi';
22
22
  import { registerPasskey } from '../utils/authService';
23
23
  import { FEATURES } from '../auth/authConfig';
24
+ import { resolveErrorText } from '../utils/resolveErrorText';
24
25
 
25
26
  export const PASSKEYS_ALERT_SX = { mb: 2 };
26
27
  export const PASSKEYS_DIVIDER_SX = { mb: 2 };
27
28
 
28
29
  export function PasskeysComponent() {
29
- const { t } = useTranslation();
30
+ const { t, i18n } = useTranslation();
30
31
 
31
32
  const [passkeys, setPasskeys] = useState([]);
32
33
  const [loading, setLoading] = useState(true);
@@ -48,7 +49,7 @@ export function PasskeysComponent() {
48
49
  const data = await fetchPasskeys();
49
50
  setPasskeys(Array.isArray(data) ? data : []);
50
51
  } catch (err) {
51
- setErrorKey(err.code || 'Auth.PASSKEY_LIST_FAILED');
52
+ setErrorKey(resolveErrorText(i18n, err, 'Auth.PASSKEY_LIST_FAILED'));
52
53
  } finally {
53
54
  setLoading(false);
54
55
  setReloading(false);
@@ -68,7 +69,7 @@ export function PasskeysComponent() {
68
69
  setErrorKey(null);
69
70
 
70
71
  if (!FEATURES.passkeysEnabled || !passkeysSupported) {
71
- setErrorKey('Auth.PASSKEY_NOT_AVAILABLE_ENV');
72
+ setErrorKey(t('Auth.PASSKEY_NOT_AVAILABLE_ENV'));
72
73
  return;
73
74
  }
74
75
 
@@ -85,7 +86,7 @@ export function PasskeysComponent() {
85
86
  await loadPasskeys();
86
87
  } catch (err) {
87
88
  // registerPasskey wirft normalisierte Errors mit .code
88
- setErrorKey(err.code || 'Auth.PASSKEY_CREATE_FAILED');
89
+ setErrorKey(resolveErrorText(i18n, err, 'Auth.PASSKEY_CREATE_FAILED'));
89
90
  } finally {
90
91
  setCreating(false);
91
92
  }
@@ -101,7 +102,7 @@ export function PasskeysComponent() {
101
102
  setPasskeys((prev) => prev.filter((pk) => pk.id !== id));
102
103
  setMessageKey('Auth.PASSKEY_DELETE_SUCCESS');
103
104
  } catch (err) {
104
- setErrorKey(err.code || 'Auth.PASSKEY_DELETE_FAILED');
105
+ setErrorKey(resolveErrorText(i18n, err, 'Auth.PASSKEY_DELETE_FAILED'));
105
106
  } finally {
106
107
  setDeletingIds((prev) => {
107
108
  const next = new Set(prev);
@@ -146,7 +147,7 @@ export function PasskeysComponent() {
146
147
  )}
147
148
  {errorKey && (
148
149
  <Alert severity="error" sx={PASSKEYS_ALERT_SX}>
149
- {t(errorKey)}
150
+ {errorKey}
150
151
  </Alert>
151
152
  )}
152
153
 
@@ -18,6 +18,7 @@ import { useTranslation } from 'react-i18next';
18
18
  // WICHTIG: Importiere den Context, um die bereits geladenen Daten zu nutzen
19
19
  import { AuthContext } from '../auth/AuthContext';
20
20
  import { fetchCookieStatement, fetchPrivacyStatement } from '../auth/authApi';
21
+ import { resolveErrorText } from '../utils/resolveErrorText';
21
22
 
22
23
  export function ProfileComponent({
23
24
  onSubmit,
@@ -29,7 +30,7 @@ export function ProfileComponent({
29
30
  privacyStatementText = null,
30
31
  cookieStatementText = null,
31
32
  }) {
32
- const { t } = useTranslation();
33
+ const { t, i18n } = useTranslation();
33
34
 
34
35
  // WICHTIG: Wir holen den User direkt aus dem globalen State
35
36
  // Das verhindert den doppelten Request und den ReferenceError
@@ -140,7 +141,7 @@ export function ProfileComponent({
140
141
  } catch (err) {
141
142
  // eslint-disable-next-line no-console
142
143
  console.error("Profile update error:", err);
143
- setErrorKey(err.code || 'Auth.PROFILE_UPDATE_FAILED');
144
+ setErrorKey(resolveErrorText(i18n, err, 'Auth.PROFILE_UPDATE_FAILED'));
144
145
  } finally {
145
146
  setSaving(false);
146
147
  }
@@ -175,7 +176,7 @@ export function ProfileComponent({
175
176
  >
176
177
  {errorKey && (
177
178
  <Alert severity="error">
178
- {t(errorKey)}
179
+ {errorKey}
179
180
  </Alert>
180
181
  )}
181
182
  {successKey && (
@@ -10,6 +10,7 @@ import {
10
10
  import { useTranslation } from 'react-i18next';
11
11
  import { QRCodeSVG } from 'qrcode.react';
12
12
  import { createSignupQr } from '../auth/authApi';
13
+ import { resolveErrorText } from '../utils/resolveErrorText';
13
14
 
14
15
  export const QR_SIGNUP_MANAGER_ALERT_SX = { mb: 2 };
15
16
 
@@ -47,7 +48,7 @@ export function QrSignupManager({
47
48
  registrationContext = null,
48
49
  defaultMaxRedemptions = DEFAULT_MAX_REDEMPTIONS,
49
50
  }) {
50
- const { t } = useTranslation();
51
+ const { t, i18n } = useTranslation();
51
52
  const qrWrapperRef = useRef(null);
52
53
  const [busy, setBusy] = useState(false);
53
54
  const [error, setError] = useState('');
@@ -112,7 +113,7 @@ export function QrSignupManager({
112
113
  hasGeneratedRef.current = true;
113
114
  setSuccess(t('Auth.SIGNUP_QR_CREATE_SUCCESS', 'New QR signup link created.'));
114
115
  } catch (err) {
115
- setError(t(err?.code || 'Auth.SIGNUP_QR_CREATE_FAILED', 'Could not create signup QR.'));
116
+ setError(resolveErrorText(i18n, err, 'Auth.SIGNUP_QR_CREATE_FAILED'));
116
117
  } finally {
117
118
  setBusy(false);
118
119
  }
@@ -158,7 +159,7 @@ export function QrSignupManager({
158
159
  setSuccess(t('Auth.SIGNUP_QR_CREATE_SUCCESS', 'New QR signup link created.'));
159
160
  } catch (err) {
160
161
  if (!active) return;
161
- setError(t(err?.code || 'Auth.SIGNUP_QR_CREATE_FAILED', 'Could not create signup QR.'));
162
+ setError(resolveErrorText(i18n, err, 'Auth.SIGNUP_QR_CREATE_FAILED'));
162
163
  } finally {
163
164
  if (active) {
164
165
  setBusy(false);
@@ -9,6 +9,7 @@ import {
9
9
  } from '@mui/material';
10
10
  import { useTranslation } from 'react-i18next';
11
11
  import { updateAuthPolicy } from '../auth/authApi';
12
+ import { resolveErrorText } from '../utils/resolveErrorText';
12
13
 
13
14
  export const QR_SIGNUP_VALIDITY_ALERT_SX = { mb: 2 };
14
15
  export const QR_SIGNUP_VALIDITY_FIELD_SX = { flex: 1 };
@@ -30,7 +31,7 @@ export function QrSignupValidityManager({
30
31
  onPolicyChange,
31
32
  canEdit = true,
32
33
  }) {
33
- const { t } = useTranslation();
34
+ const { t, i18n } = useTranslation();
34
35
  const [currentExpiryDays, setCurrentExpiryDays] = useState(String(expiryDays || DEFAULT_EXPIRY_DAYS));
35
36
  const [busy, setBusy] = useState(false);
36
37
  const [error, setError] = useState('');
@@ -53,7 +54,7 @@ export function QrSignupValidityManager({
53
54
  setSuccess(t('Auth.AUTH_POLICY_SAVE_SUCCESS', 'Authentication settings saved.'));
54
55
  if (onPolicyChange) onPolicyChange(next);
55
56
  } catch (err) {
56
- setError(t(err?.code || 'Auth.AUTH_POLICY_UPDATE_FAILED', 'Could not save authentication settings.'));
57
+ setError(resolveErrorText(i18n, err, 'Auth.AUTH_POLICY_UPDATE_FAILED'));
57
58
  } finally {
58
59
  setBusy(false);
59
60
  }
@@ -10,6 +10,7 @@ import {
10
10
  } from '@mui/material';
11
11
  import { useTranslation } from 'react-i18next';
12
12
  import { updateAuthPolicy } from '../auth/authApi';
13
+ import { resolveErrorText } from '../utils/resolveErrorText';
13
14
 
14
15
  export const REGISTRATION_METHODS_ALERT_SX = { mb: 2 };
15
16
  export const REGISTRATION_METHODS_INFO_SX = { mt: 2 };
@@ -31,7 +32,7 @@ export function RegistrationMethodsManager({
31
32
  onPolicyChange,
32
33
  canEdit = true,
33
34
  }) {
34
- const { t } = useTranslation();
35
+ const { t, i18n } = useTranslation();
35
36
  const [policyState, setPolicyState] = useState(EMPTY_POLICY);
36
37
  const [busyField, setBusyField] = useState('');
37
38
  const [saveError, setSaveError] = useState('');
@@ -57,7 +58,7 @@ export function RegistrationMethodsManager({
57
58
  if (onPolicyChange) onPolicyChange(next);
58
59
  } catch (err) {
59
60
  setPolicyState((prev) => ({ ...prev, [field]: previous }));
60
- setSaveError(t(err?.code || 'Auth.AUTH_POLICY_UPDATE_FAILED', 'Could not save authentication settings.'));
61
+ setSaveError(resolveErrorText(i18n, err, 'Auth.AUTH_POLICY_UPDATE_FAILED'));
61
62
  } finally {
62
63
  setBusyField('');
63
64
  }
@@ -14,6 +14,7 @@ import { MFAComponent } from './MFAComponent';
14
14
  import { changePassword } from '../auth/authApi';
15
15
  import { startSocialLogin } from '../utils/authService';
16
16
  import { AuthContext } from '../auth/AuthContext';
17
+ import { resolveErrorText } from '../utils/resolveErrorText';
17
18
 
18
19
  export const SECURITY_ALERT_SX = { mb: 2 };
19
20
  export const SECURITY_DIVIDER_SX = { my: 3 };
@@ -22,7 +23,7 @@ export function SecurityComponent({
22
23
  fromRecovery = false,
23
24
  fromWeakLogin = false,
24
25
  }) {
25
- const { t } = useTranslation();
26
+ const { t, i18n } = useTranslation();
26
27
  const { authMethods } = useContext(AuthContext);
27
28
 
28
29
  const [messageKey, setMessageKey] = useState(null);
@@ -60,7 +61,7 @@ export function SecurityComponent({
60
61
  callbackUrl: `${window.location.origin}/account?tab=security`,
61
62
  });
62
63
  } catch (err) {
63
- setErrorKey(err.code || 'Auth.SOCIAL_LOGIN_FAILED');
64
+ setErrorKey(resolveErrorText(i18n, err, 'Auth.SOCIAL_LOGIN_FAILED'));
64
65
  }
65
66
  };
66
67
 
@@ -71,7 +72,7 @@ export function SecurityComponent({
71
72
  await changePassword(currentPassword, newPassword);
72
73
  setMessageKey('Auth.RESET_PASSWORD_SUCCESS');
73
74
  } catch (err) {
74
- setErrorKey(err.code || 'Auth.PASSWORD_CHANGE_FAILED');
75
+ setErrorKey(resolveErrorText(i18n, err, 'Auth.PASSWORD_CHANGE_FAILED'));
75
76
  }
76
77
  };
77
78
 
@@ -96,7 +97,7 @@ export function SecurityComponent({
96
97
  )}
97
98
  {errorKey && (
98
99
  <Alert severity="error" sx={SECURITY_ALERT_SX}>
99
- {t(errorKey)}
100
+ {errorKey}
100
101
  </Alert>
101
102
  )}
102
103
 
@@ -24,6 +24,7 @@ import {
24
24
  } from '@mui/material';
25
25
  import CloseIcon from '@mui/icons-material/Close';
26
26
  import { useTranslation } from 'react-i18next';
27
+ import { resolveErrorText } from '../utils/resolveErrorText';
27
28
  import { AuthContext } from '../auth/AuthContext';
28
29
  import {
29
30
  approveRecoveryRequest,
@@ -63,7 +64,7 @@ export function SupportRecoveryRequestsTab() {
63
64
  const data = await fetchRecoveryRequests(statusFilter);
64
65
  setRequests(Array.isArray(data) ? data : []);
65
66
  } catch (err) {
66
- setErrorKey(err.code || 'Support.RECOVERY_REQUESTS_LOAD_FAILED');
67
+ setErrorKey(resolveErrorText(i18n, err, 'Support.RECOVERY_REQUESTS_LOAD_FAILED'));
67
68
  } finally {
68
69
  setLoading(false);
69
70
  }
@@ -88,7 +89,7 @@ export function SupportRecoveryRequestsTab() {
88
89
  });
89
90
  setAllUsers(list);
90
91
  } catch (err) {
91
- setAgentErrorKey(err.code || 'Auth.USER_LIST_FAILED');
92
+ setAgentErrorKey(resolveErrorText(i18n, err, 'Auth.USER_LIST_FAILED'));
92
93
  } finally {
93
94
  setAgentLoading(false);
94
95
  }
@@ -124,7 +125,7 @@ export function SupportRecoveryRequestsTab() {
124
125
  await loadRequests();
125
126
  closeDialog();
126
127
  } catch (err) {
127
- setErrorKey(err.code || 'Support.RECOVERY_REQUEST_APPROVE_FAILED');
128
+ setErrorKey(resolveErrorText(i18n, err, 'Support.RECOVERY_REQUEST_APPROVE_FAILED'));
128
129
  }
129
130
  };
130
131
 
@@ -136,7 +137,7 @@ export function SupportRecoveryRequestsTab() {
136
137
  await loadRequests();
137
138
  closeDialog();
138
139
  } catch (err) {
139
- setErrorKey(err.code || 'Support.RECOVERY_REQUEST_REJECT_FAILED');
140
+ setErrorKey(resolveErrorText(i18n, err, 'Support.RECOVERY_REQUEST_REJECT_FAILED'));
140
141
  }
141
142
  };
142
143
 
@@ -167,7 +168,7 @@ export function SupportRecoveryRequestsTab() {
167
168
  setSelectedAgentCandidate(null);
168
169
  await loadUsers();
169
170
  } catch (err) {
170
- setAgentErrorKey(err.code || 'Auth.USER_SUPPORT_UPDATE_FAILED');
171
+ setAgentErrorKey(resolveErrorText(i18n, err, 'Auth.USER_SUPPORT_UPDATE_FAILED'));
171
172
  } finally {
172
173
  setAgentActionUserId(null);
173
174
  }
@@ -185,7 +186,7 @@ export function SupportRecoveryRequestsTab() {
185
186
  }
186
187
  await loadUsers();
187
188
  } catch (err) {
188
- setAgentErrorKey(err.code || 'Auth.USER_SUPPORT_UPDATE_FAILED');
189
+ setAgentErrorKey(resolveErrorText(i18n, err, 'Auth.USER_SUPPORT_UPDATE_FAILED'));
189
190
  } finally {
190
191
  setAgentActionUserId(null);
191
192
  }
@@ -216,7 +217,7 @@ export function SupportRecoveryRequestsTab() {
216
217
 
217
218
  {agentErrorKey && (
218
219
  <Alert severity="error" sx={SUPPORT_RECOVERY_ALERT_SX}>
219
- {t(agentErrorKey)}
220
+ {agentErrorKey}
220
221
  </Alert>
221
222
  )}
222
223
 
@@ -310,7 +311,7 @@ export function SupportRecoveryRequestsTab() {
310
311
 
311
312
  {errorKey && (
312
313
  <Alert severity="error" sx={SUPPORT_RECOVERY_ALERT_SX}>
313
- {t(errorKey)}
314
+ {errorKey}
314
315
  </Alert>
315
316
  )}
316
317
 
@@ -2,6 +2,7 @@ import React, { useState } from 'react';
2
2
  import { Box, TextField, Button, Typography, Alert, CircularProgress } from '@mui/material';
3
3
  import { sendAdminInvite } from '../auth/authApi';
4
4
  import { useTranslation } from 'react-i18next';
5
+ import { resolveErrorText } from '../utils/resolveErrorText';
5
6
 
6
7
  export const USER_INVITE_ACTION_SX = {
7
8
  minWidth: 120,
@@ -11,7 +12,7 @@ export const USER_INVITE_ACTION_SX = {
11
12
  export const USER_INVITE_ALERT_SX = { mb: 2 };
12
13
 
13
14
  export function UserInviteComponent() { // FIX: Removed apiUrl prop
14
- const { t } = useTranslation();
15
+ const { t, i18n } = useTranslation();
15
16
  const [inviteEmail, setInviteEmail] = useState('');
16
17
  const [message, setMessage] = useState('');
17
18
  const [error, setError] = useState('');
@@ -31,7 +32,7 @@ export function UserInviteComponent() { // FIX: Removed apiUrl prop
31
32
  // err.message contains normalized text or code from authApi
32
33
  // eslint-disable-next-line no-console
33
34
  console.error('Error inviting user:', err);
34
- setError(t(err.code) || err.message || t('Auth.INVITE_FAILED'));
35
+ setError(resolveErrorText(i18n, err, 'Auth.INVITE_FAILED'));
35
36
  } finally {
36
37
  setLoading(false);
37
38
  }
@@ -24,6 +24,7 @@ import CancelIcon from '@mui/icons-material/Cancel';
24
24
  import DeleteIcon from '@mui/icons-material/Delete';
25
25
  import { useTranslation } from 'react-i18next';
26
26
  import { fetchUsersList, deleteUser, updateUserRole } from '../auth/authApi';
27
+ import { resolveErrorText } from '../utils/resolveErrorText';
27
28
 
28
29
  const DEFAULT_ROLES = ['none', 'student', 'teacher', 'admin'];
29
30
 
@@ -49,7 +50,7 @@ export function UserListComponent({
49
50
  canDeleteUser = null,
50
51
  onDeleteUser = null,
51
52
  }) {
52
- const { t } = useTranslation();
53
+ const { t, i18n } = useTranslation();
53
54
  const [users, setUsers] = useState([]);
54
55
  const [loading, setLoading] = useState(true);
55
56
  const [error, setError] = useState(null);
@@ -75,7 +76,7 @@ export function UserListComponent({
75
76
  });
76
77
  setUsers(list);
77
78
  } catch (err) {
78
- setError(err.code || 'Auth.USER_LIST_FAILED');
79
+ setError(resolveErrorText(i18n, err, 'Auth.USER_LIST_FAILED'));
79
80
  } finally {
80
81
  setLoading(false);
81
82
  }
@@ -99,7 +100,7 @@ export function UserListComponent({
99
100
  }
100
101
  setUsers(prev => prev.filter(u => u.id !== userId));
101
102
  } catch (err) {
102
- alert(t(err.code || 'Auth.USER_DELETE_FAILED'));
103
+ alert(resolveErrorText(i18n, err, 'Auth.USER_DELETE_FAILED'));
103
104
  }
104
105
  };
105
106
 
@@ -120,7 +121,7 @@ export function UserListComponent({
120
121
  await loadUsers();
121
122
  alert(t('UserList.ROLE_UPDATE_SUCCESS', 'Role updated.'));
122
123
  } catch (err) {
123
- alert(t(err.code || 'Auth.USER_ROLE_UPDATE_FAILED'));
124
+ alert(resolveErrorText(i18n, err, 'Auth.USER_ROLE_UPDATE_FAILED'));
124
125
  }
125
126
  };
126
127
 
@@ -276,7 +277,7 @@ export function UserListComponent({
276
277
  });
277
278
  } catch (err) {
278
279
  // eslint-disable-next-line no-alert
279
- alert(err?.message || t('Common.OPERATION_FAILED', 'Operation failed.'));
280
+ alert(resolveErrorText(i18n, err, 'Common.OPERATION_FAILED'));
280
281
  } finally {
281
282
  setRowActionLoading((prev) => ({ ...prev, [actionId]: false }));
282
283
  }
@@ -514,7 +515,7 @@ export function UserListComponent({
514
515
  />
515
516
  </Box>
516
517
 
517
- {error && <Alert severity="error" sx={USER_LIST_ALERT_SX}>{t(error)}</Alert>}
518
+ {error && <Alert severity="error" sx={USER_LIST_ALERT_SX}>{error}</Alert>}
518
519
 
519
520
  {loading && (
520
521
  <Box sx={{ display: 'flex', justifyContent: 'center', p: 3 }}>