@micha.bigler/ui-core-micha 1.4.38 → 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.
package/dist/auth/authApi.js
CHANGED
|
@@ -388,12 +388,9 @@ export async function updateUserRole(userId, newRole) {
|
|
|
388
388
|
*/
|
|
389
389
|
export async function updateUserSupportStatus(userId, isSupportAgent) {
|
|
390
390
|
try {
|
|
391
|
-
//
|
|
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
|
-
|
|
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;
|
|
@@ -965,5 +965,56 @@ export const authTranslations = {
|
|
|
965
965
|
"de": "Nein",
|
|
966
966
|
"fr": "Non",
|
|
967
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."
|
|
968
1019
|
}
|
|
969
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
|
-
//
|
|
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
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* - Permissions:
|
|
21
|
-
* -
|
|
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.
|
|
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.
|
|
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.
|
|
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
package/src/auth/authApi.jsx
CHANGED
|
@@ -402,12 +402,9 @@ export async function updateUserRole(userId, newRole) {
|
|
|
402
402
|
*/
|
|
403
403
|
export async function updateUserSupportStatus(userId, isSupportAgent) {
|
|
404
404
|
try {
|
|
405
|
-
//
|
|
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
|
-
|
|
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;
|
|
@@ -1012,5 +1012,57 @@ export const authTranslations = {
|
|
|
1012
1012
|
"de": "Nein",
|
|
1013
1013
|
"fr": "Non",
|
|
1014
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."
|
|
1015
1067
|
}
|
|
1016
1068
|
};
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
} from '@mui/material';
|
|
12
12
|
import { useTranslation } from 'react-i18next';
|
|
13
13
|
|
|
14
|
-
//
|
|
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
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* - Permissions:
|
|
29
|
-
* -
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|