@micha.bigler/ui-core-micha 1.4.37 → 1.4.39

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.
@@ -388,12 +388,9 @@ export async function updateUserRole(userId, newRole) {
388
388
  */
389
389
  export async function updateUserSupportStatus(userId, isSupportAgent) {
390
390
  try {
391
- // Wir gehen davon aus, dass dein Serializer "profile" nested akzeptiert
392
- // (siehe BaseUserSerializer.update Logik im Backend)
391
+ // Backend expects flat field names, DRF maps via `source="profile.is_support_agent"`
393
392
  const payload = {
394
- profile: {
395
- is_support_agent: isSupportAgent,
396
- },
393
+ is_support_agent: isSupportAgent,
397
394
  };
398
395
  const res = await apiClient.patch(`${USERS_BASE}/${userId}/`, payload);
399
396
  return res.data;
@@ -36,6 +36,6 @@ export function SecurityComponent({ fromRecovery = false, fromWeakLogin = false,
36
36
  setErrorKey(err.code || 'Auth.PASSWORD_CHANGE_FAILED');
37
37
  }
38
38
  };
39
- return (_jsxs(Box, { children: [fromRecovery && (_jsx(Alert, { severity: "warning", sx: { mb: 2 }, children: t('Security.RECOVERY_LOGIN_WARNING') })), fromWeakLogin && (_jsx(Alert, { severity: "warning", sx: { mb: 2 }, children: t('Security.WEAK_LOGIN_WARNING') })), messageKey && (_jsx(Alert, { severity: "success", sx: { mb: 2 }, children: t(messageKey) })), errorKey && (_jsx(Alert, { severity: "error", sx: { mb: 2 }, children: t(errorKey) })), _jsx(Typography, { variant: "h6", gutterBottom: true, children: t('Security.LOGIN_PASSWORD_LABEL') }), _jsx(PasswordChangeForm, { onSubmit: handlePasswordChange }), _jsx(Divider, { sx: { my: 3 } }), _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 }), _jsx(Divider, { sx: { my: 3 } }), _jsx(PasskeysComponent, {}), _jsx(Divider, { sx: { my: 3 } }), _jsx(MFAComponent, {})] }));
39
+ return (_jsxs(Box, { children: [fromRecovery && (_jsx(Alert, { severity: "warning", sx: { mb: 2 }, children: t('Security.RECOVERY_LOGIN_WARNING') })), fromWeakLogin && (_jsx(Alert, { severity: "warning", sx: { mb: 2 }, children: t('Security.WEAK_LOGIN_WARNING') })), messageKey && (_jsx(Alert, { severity: "success", sx: { mb: 2 }, children: t(messageKey) })), errorKey && (_jsx(Alert, { severity: "error", sx: { mb: 2 }, children: t(errorKey) })), _jsx(Typography, { variant: "h6", gutterBottom: true, children: t('Auth.LOGIN_PASSWORD_LABEL') }), _jsx(PasswordChangeForm, { onSubmit: handlePasswordChange }), _jsx(Divider, { sx: { my: 3 } }), _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 }), _jsx(Divider, { sx: { my: 3 } }), _jsx(PasskeysComponent, {}), _jsx(Divider, { sx: { my: 3 } }), _jsx(MFAComponent, {})] }));
40
40
  }
41
41
  ;
@@ -955,5 +955,66 @@ export const authTranslations = {
955
955
  "de": "Abgelaufen",
956
956
  "fr": "Expirée",
957
957
  "en": "Expired"
958
+ },
959
+ "Common.Yes": {
960
+ "de": "Ja",
961
+ "fr": "Oui",
962
+ "en": "Yes"
963
+ },
964
+ "Common.No": {
965
+ "de": "Nein",
966
+ "fr": "Non",
967
+ "en": "No"
968
+ },
969
+ "Account.TITLE": {
970
+ "de": "Konto & Verwaltung",
971
+ "fr": "Compte et administration",
972
+ "en": "Account & Administration"
973
+ },
974
+ "Account.PAGE_TITLE": {
975
+ "de": "Konto",
976
+ "fr": "Compte",
977
+ "en": "Account"
978
+ },
979
+ "Account.TAB_PROFILE": {
980
+ "de": "Profil",
981
+ "fr": "Profil",
982
+ "en": "Profile"
983
+ },
984
+ "Account.TAB_SECURITY": {
985
+ "de": "Sicherheit",
986
+ "fr": "Sécurité",
987
+ "en": "Security"
988
+ },
989
+ "Account.TAB_USERS": {
990
+ "de": "Benutzer",
991
+ "fr": "Utilisateurs",
992
+ "en": "Users"
993
+ },
994
+ "Account.TAB_INVITE": {
995
+ "de": "Einladen",
996
+ "fr": "Inviter",
997
+ "en": "Invite"
998
+ },
999
+ "Account.TAB_ACCESS_CODES": {
1000
+ "de": "Zugangscodes",
1001
+ "fr": "Codes d'accès",
1002
+ "en": "Access codes"
1003
+ },
1004
+ "Account.TAB_SUPPORT": {
1005
+ "de": "Support",
1006
+ "fr": "Support",
1007
+ "en": "Support"
1008
+ },
1009
+ "Account.ACCESS_CODES_HINT": {
1010
+ "de": "Verwalten Sie Zugangscodes für die Selbstregistrierung.",
1011
+ "fr": "Gérer les codes d'accès pour l'auto-inscription.",
1012
+ "en": "Manage access codes for self-registration."
1013
+ },
1014
+ // --- Missing Auth Keys ---
1015
+ "Auth.NOT_LOGGED_IN": {
1016
+ "de": "Benutzer nicht angemeldet.",
1017
+ "fr": "Utilisateur non connecté.",
1018
+ "en": "User not logged in."
958
1019
  }
959
1020
  };
@@ -4,7 +4,7 @@ import { Helmet } from 'react-helmet';
4
4
  import { useSearchParams } from 'react-router-dom';
5
5
  import { Tabs, Tab, Box, Typography, Alert, CircularProgress } from '@mui/material';
6
6
  import { useTranslation } from 'react-i18next';
7
- // Interne Komponenten der Library
7
+ // Internal Library Components
8
8
  import { WidePage } from '../layout/PageLayout';
9
9
  import { ProfileComponent } from '../components/ProfileComponent';
10
10
  import { SecurityComponent } from '../components/SecurityComponent';
@@ -15,11 +15,10 @@ import { SupportRecoveryRequestsTab } from '../components/SupportRecoveryRequest
15
15
  import { AuthContext } from '../auth/AuthContext';
16
16
  import { updateUserProfile } from '../auth/authApi';
17
17
  /**
18
- * Vollständige, selbst-konfigurierende Account-Seite.
19
- * * Architektur:
20
- * - Permissions: Werden aus user.ui_permissions gelesen (vom Backend).
21
- * - Rollen: Werden aus user.available_roles gelesen (vom Backend).
22
- * - Keine Props mehr nötig -> Plug & Play in der App.js.
18
+ * Complete, self-configuring Account Page.
19
+ * Architecture:
20
+ * - Permissions: Read from user.ui_permissions (from Backend).
21
+ * - Roles: Read from user.available_roles (from Backend).
23
22
  */
24
23
  export function AccountPage() {
25
24
  const { t } = useTranslation();
@@ -29,10 +28,8 @@ export function AccountPage() {
29
28
  const currentTab = searchParams.get('tab') || 'profile';
30
29
  const fromRecovery = searchParams.get('from') === 'recovery';
31
30
  const fromWeakLogin = searchParams.get('from') === 'weak_login';
32
- // 2. Daten & Permissions extrahieren (Single Source of Truth)
33
- // Backend liefert die Liste der Rollen (Keys), z.B. ['student', 'teacher']
31
+ // 2. Data & Permissions (Single Source of Truth)
34
32
  const activeRoles = (user === null || user === void 0 ? void 0 : user.available_roles) || [];
35
- // Backend liefert Permissions basierend auf settings.py
36
33
  const perms = (user === null || user === void 0 ? void 0 : user.ui_permissions) || {};
37
34
  const handleTabChange = (_event, newValue) => {
38
35
  setSearchParams({ tab: newValue });
@@ -41,9 +38,8 @@ export function AccountPage() {
41
38
  const updatedUser = await updateUserProfile(payload);
42
39
  login(updatedUser);
43
40
  };
44
- // 3. Dynamische Tabs bauen
41
+ // 3. Dynamic Tabs
45
42
  const tabs = useMemo(() => {
46
- // Wenn User noch nicht da ist, leere Liste (Loading State fängt das ab)
47
43
  if (!user)
48
44
  return [];
49
45
  const list = [
@@ -71,8 +67,7 @@ export function AccountPage() {
71
67
  if (!user) {
72
68
  return (_jsx(WidePage, { children: _jsx(Alert, { severity: "warning", children: t('Auth.NOT_LOGGED_IN', 'User not logged in.') }) }));
73
69
  }
74
- // 5. Sicherheits-Check: Ist der aktuelle Tab überhaupt erlaubt?
75
- // Verhindert Zugriff durch URL-Manipulation (z.B. ?tab=users eingeben ohne Admin-Rechte)
70
+ // 5. Security Check: Is the active tab allowed?
76
71
  const activeTabExists = tabs.some(t => t.value === currentTab);
77
72
  const safeTab = activeTabExists ? currentTab : 'profile';
78
73
  return (_jsxs(WidePage, { title: t('Account.TITLE', 'Account & Administration'), children: [_jsx(Helmet, { children: _jsxs("title", { children: [t('Account.PAGE_TITLE', 'Account'), " \u2013 ", user.email] }) }), _jsx(Tabs, { value: safeTab, onChange: handleTabChange, variant: "scrollable", scrollButtons: "auto", sx: { mb: 3, borderBottom: 1, borderColor: 'divider' }, children: tabs.map((tab) => (_jsx(Tab, { label: tab.label, value: tab.value }, tab.value))) }), safeTab === 'profile' && (_jsx(Box, { sx: { mt: 2 }, children: _jsx(ProfileComponent, { onSubmit: handleProfileSubmit, showName: true, showPrivacy: true, showCookies: true }) })), safeTab === 'security' && (_jsx(Box, { sx: { mt: 2 }, children: _jsx(SecurityComponent, { fromRecovery: fromRecovery, fromWeakLogin: fromWeakLogin }) })), safeTab === 'users' && (_jsx(Box, { sx: { mt: 2 }, children: _jsx(UserListComponent, { roles: activeRoles, currentUser: user }) })), safeTab === 'invite' && (_jsx(Box, { sx: { mt: 2 }, children: _jsx(UserInviteComponent, {}) })), safeTab === 'access' && (_jsxs(Box, { sx: { mt: 2 }, children: [_jsx(Typography, { variant: "body2", sx: { mb: 2, color: 'text.secondary' }, children: t('Account.ACCESS_CODES_HINT', 'Manage access codes for self-registration.') }), _jsx(AccessCodeManager, {})] })), safeTab === 'support' && (_jsx(Box, { sx: { mt: 2 }, children: _jsx(SupportRecoveryRequestsTab, {}) }))] }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@micha.bigler/ui-core-micha",
3
- "version": "1.4.37",
3
+ "version": "1.4.39",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/index.js",
6
6
  "private": false,
@@ -402,12 +402,9 @@ export async function updateUserRole(userId, newRole) {
402
402
  */
403
403
  export async function updateUserSupportStatus(userId, isSupportAgent) {
404
404
  try {
405
- // Wir gehen davon aus, dass dein Serializer "profile" nested akzeptiert
406
- // (siehe BaseUserSerializer.update Logik im Backend)
405
+ // Backend expects flat field names, DRF maps via `source="profile.is_support_agent"`
407
406
  const payload = {
408
- profile: {
409
- is_support_agent: isSupportAgent,
410
- },
407
+ is_support_agent: isSupportAgent,
411
408
  };
412
409
  const res = await apiClient.patch(`${USERS_BASE}/${userId}/`, payload);
413
410
  return res.data;
@@ -74,7 +74,7 @@ export function SecurityComponent({
74
74
 
75
75
  {/* Password section */}
76
76
  <Typography variant="h6" gutterBottom>
77
- {t('Security.LOGIN_PASSWORD_LABEL')}
77
+ {t('Auth.LOGIN_PASSWORD_LABEL')}
78
78
  </Typography>
79
79
  <PasswordChangeForm onSubmit={handlePasswordChange} />
80
80
 
@@ -1002,5 +1002,67 @@ export const authTranslations = {
1002
1002
  "de": "Abgelaufen",
1003
1003
  "fr": "Expirée",
1004
1004
  "en": "Expired"
1005
+ },
1006
+ "Common.Yes": {
1007
+ "de": "Ja",
1008
+ "fr": "Oui",
1009
+ "en": "Yes"
1010
+ },
1011
+ "Common.No": {
1012
+ "de": "Nein",
1013
+ "fr": "Non",
1014
+ "en": "No"
1015
+ },
1016
+ "Account.TITLE": {
1017
+ "de": "Konto & Verwaltung",
1018
+ "fr": "Compte et administration",
1019
+ "en": "Account & Administration"
1020
+ },
1021
+ "Account.PAGE_TITLE": {
1022
+ "de": "Konto",
1023
+ "fr": "Compte",
1024
+ "en": "Account"
1025
+ },
1026
+ "Account.TAB_PROFILE": {
1027
+ "de": "Profil",
1028
+ "fr": "Profil",
1029
+ "en": "Profile"
1030
+ },
1031
+ "Account.TAB_SECURITY": {
1032
+ "de": "Sicherheit",
1033
+ "fr": "Sécurité",
1034
+ "en": "Security"
1035
+ },
1036
+ "Account.TAB_USERS": {
1037
+ "de": "Benutzer",
1038
+ "fr": "Utilisateurs",
1039
+ "en": "Users"
1040
+ },
1041
+ "Account.TAB_INVITE": {
1042
+ "de": "Einladen",
1043
+ "fr": "Inviter",
1044
+ "en": "Invite"
1045
+ },
1046
+ "Account.TAB_ACCESS_CODES": {
1047
+ "de": "Zugangscodes",
1048
+ "fr": "Codes d'accès",
1049
+ "en": "Access codes"
1050
+ },
1051
+ "Account.TAB_SUPPORT": {
1052
+ "de": "Support",
1053
+ "fr": "Support",
1054
+ "en": "Support"
1055
+ },
1056
+ "Account.ACCESS_CODES_HINT": {
1057
+ "de": "Verwalten Sie Zugangscodes für die Selbstregistrierung.",
1058
+ "fr": "Gérer les codes d'accès pour l'auto-inscription.",
1059
+ "en": "Manage access codes for self-registration."
1060
+ },
1061
+
1062
+ // --- Missing Auth Keys ---
1063
+ "Auth.NOT_LOGGED_IN": {
1064
+ "de": "Benutzer nicht angemeldet.",
1065
+ "fr": "Utilisateur non connecté.",
1066
+ "en": "User not logged in."
1005
1067
  }
1006
1068
  };
@@ -11,7 +11,7 @@ import {
11
11
  } from '@mui/material';
12
12
  import { useTranslation } from 'react-i18next';
13
13
 
14
- // Interne Komponenten der Library
14
+ // Internal Library Components
15
15
  import { WidePage } from '../layout/PageLayout';
16
16
  import { ProfileComponent } from '../components/ProfileComponent';
17
17
  import { SecurityComponent } from '../components/SecurityComponent';
@@ -23,11 +23,10 @@ import { AuthContext } from '../auth/AuthContext';
23
23
  import { updateUserProfile } from '../auth/authApi';
24
24
 
25
25
  /**
26
- * Vollständige, selbst-konfigurierende Account-Seite.
27
- * * Architektur:
28
- * - Permissions: Werden aus user.ui_permissions gelesen (vom Backend).
29
- * - Rollen: Werden aus user.available_roles gelesen (vom Backend).
30
- * - Keine Props mehr nötig -> Plug & Play in der App.js.
26
+ * Complete, self-configuring Account Page.
27
+ * Architecture:
28
+ * - Permissions: Read from user.ui_permissions (from Backend).
29
+ * - Roles: Read from user.available_roles (from Backend).
31
30
  */
32
31
  export function AccountPage() {
33
32
  const { t } = useTranslation();
@@ -39,11 +38,8 @@ export function AccountPage() {
39
38
  const fromRecovery = searchParams.get('from') === 'recovery';
40
39
  const fromWeakLogin = searchParams.get('from') === 'weak_login';
41
40
 
42
- // 2. Daten & Permissions extrahieren (Single Source of Truth)
43
- // Backend liefert die Liste der Rollen (Keys), z.B. ['student', 'teacher']
41
+ // 2. Data & Permissions (Single Source of Truth)
44
42
  const activeRoles = user?.available_roles || [];
45
-
46
- // Backend liefert Permissions basierend auf settings.py
47
43
  const perms = user?.ui_permissions || {};
48
44
 
49
45
  const handleTabChange = (_event, newValue) => {
@@ -55,9 +51,8 @@ export function AccountPage() {
55
51
  login(updatedUser);
56
52
  };
57
53
 
58
- // 3. Dynamische Tabs bauen
54
+ // 3. Dynamic Tabs
59
55
  const tabs = useMemo(() => {
60
- // Wenn User noch nicht da ist, leere Liste (Loading State fängt das ab)
61
56
  if (!user) return [];
62
57
 
63
58
  const list = [
@@ -103,8 +98,7 @@ export function AccountPage() {
103
98
  );
104
99
  }
105
100
 
106
- // 5. Sicherheits-Check: Ist der aktuelle Tab überhaupt erlaubt?
107
- // Verhindert Zugriff durch URL-Manipulation (z.B. ?tab=users eingeben ohne Admin-Rechte)
101
+ // 5. Security Check: Is the active tab allowed?
108
102
  const activeTabExists = tabs.some(t => t.value === currentTab);
109
103
  const safeTab = activeTabExists ? currentTab : 'profile';
110
104