@micha.bigler/ui-core-micha 2.1.16 → 2.1.17
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/AuthContext.js +7 -14
- package/dist/auth/authApi.js +15 -0
- package/dist/components/AccessCodeManager.js +1 -1
- package/dist/components/BulkInviteCsvTab.js +1 -1
- package/dist/components/ProfileComponent.js +65 -5
- package/dist/components/UserInviteComponent.js +1 -1
- package/dist/components/UserListComponent.js +163 -21
- package/dist/i18n/authTranslations.js +30 -0
- package/dist/pages/AccountPage.js +9 -15
- package/package.json +1 -1
- package/src/auth/AuthContext.jsx +36 -15
- package/src/auth/authApi.jsx +17 -1
- package/src/components/AccessCodeManager.jsx +1 -1
- package/src/components/BulkInviteCsvTab.jsx +1 -1
- package/src/components/ProfileComponent.jsx +117 -4
- package/src/components/UserInviteComponent.jsx +2 -2
- package/src/components/UserListComponent.jsx +252 -40
- package/src/i18n/authTranslations.ts +31 -1
- package/src/pages/AccountPage.jsx +32 -31
package/src/auth/AuthContext.jsx
CHANGED
|
@@ -16,6 +16,39 @@ export const AuthProvider = ({ children }) => {
|
|
|
16
16
|
const [user, setUser] = useState(null);
|
|
17
17
|
const [loading, setLoading] = useState(true);
|
|
18
18
|
|
|
19
|
+
const mapUserFromApi = (data) => {
|
|
20
|
+
const profile = data?.profile || {};
|
|
21
|
+
return {
|
|
22
|
+
...data,
|
|
23
|
+
id: data?.id,
|
|
24
|
+
username: data?.username,
|
|
25
|
+
email: data?.email,
|
|
26
|
+
first_name: data?.first_name,
|
|
27
|
+
last_name: data?.last_name,
|
|
28
|
+
role: data?.role ?? profile?.role ?? null,
|
|
29
|
+
language: data?.language ?? profile?.language ?? 'en',
|
|
30
|
+
is_superuser: Boolean(data?.is_superuser),
|
|
31
|
+
is_new: Boolean(data?.is_new ?? profile?.is_new),
|
|
32
|
+
is_invited: Boolean(data?.is_invited ?? profile?.is_invited),
|
|
33
|
+
accepted_privacy_statement: Boolean(
|
|
34
|
+
data?.accepted_privacy_statement ?? profile?.accepted_privacy_statement
|
|
35
|
+
),
|
|
36
|
+
accepted_convenience_cookies: Boolean(
|
|
37
|
+
data?.accepted_convenience_cookies ?? profile?.accepted_convenience_cookies
|
|
38
|
+
),
|
|
39
|
+
is_support_agent: Boolean(data?.is_support_agent ?? profile?.is_support_agent),
|
|
40
|
+
support_contact_id: data?.support_contact_id ?? profile?.support_contact_id ?? null,
|
|
41
|
+
security_state: data?.security_state,
|
|
42
|
+
available_roles: data?.available_roles || [],
|
|
43
|
+
ui_permissions: data?.ui_permissions || {},
|
|
44
|
+
can_manage_support_agents: Boolean(data?.can_manage_support_agents),
|
|
45
|
+
can_manage: Boolean(data?.can_manage),
|
|
46
|
+
is_active: data?.is_active,
|
|
47
|
+
last_login: data?.last_login,
|
|
48
|
+
date_joined: data?.date_joined,
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
|
|
19
52
|
useEffect(() => {
|
|
20
53
|
let isMounted = true;
|
|
21
54
|
|
|
@@ -28,19 +61,7 @@ export const AuthProvider = ({ children }) => {
|
|
|
28
61
|
const data = await fetchCurrentUser();
|
|
29
62
|
|
|
30
63
|
if (isMounted) {
|
|
31
|
-
|
|
32
|
-
setUser({
|
|
33
|
-
id: data.id,
|
|
34
|
-
username: data.username,
|
|
35
|
-
email: data.email,
|
|
36
|
-
first_name: data.first_name,
|
|
37
|
-
last_name: data.last_name,
|
|
38
|
-
role: data.role,
|
|
39
|
-
is_superuser: data.is_superuser,
|
|
40
|
-
security_state: data.security_state,
|
|
41
|
-
available_roles: data.available_roles,
|
|
42
|
-
ui_permissions: data.ui_permissions,
|
|
43
|
-
});
|
|
64
|
+
setUser(mapUserFromApi(data));
|
|
44
65
|
}
|
|
45
66
|
} catch (err) {
|
|
46
67
|
// Silent failure on 401/403 is expected (user not logged in)
|
|
@@ -58,7 +79,7 @@ export const AuthProvider = ({ children }) => {
|
|
|
58
79
|
const login = (userData) => {
|
|
59
80
|
setUser((prev) => ({
|
|
60
81
|
...prev,
|
|
61
|
-
...userData,
|
|
82
|
+
...mapUserFromApi(userData),
|
|
62
83
|
}));
|
|
63
84
|
};
|
|
64
85
|
|
|
@@ -85,4 +106,4 @@ export const AuthProvider = ({ children }) => {
|
|
|
85
106
|
{children}
|
|
86
107
|
</AuthContext.Provider>
|
|
87
108
|
);
|
|
88
|
-
};
|
|
109
|
+
};
|
package/src/auth/authApi.jsx
CHANGED
|
@@ -27,6 +27,22 @@ export async function updateUserProfile(data) {
|
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
function normalizeStatementText(data) {
|
|
31
|
+
if (typeof data === 'string') return data;
|
|
32
|
+
if (data && typeof data.content === 'string') return data.content;
|
|
33
|
+
return '';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function fetchPrivacyStatement() {
|
|
37
|
+
const res = await apiClient.get('/api/utils/privacy/');
|
|
38
|
+
return normalizeStatementText(res.data);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export async function fetchCookieStatement() {
|
|
42
|
+
const res = await apiClient.get('/api/utils/cookie/');
|
|
43
|
+
return normalizeStatementText(res.data);
|
|
44
|
+
}
|
|
45
|
+
|
|
30
46
|
export async function fetchHeadlessSession() {
|
|
31
47
|
const res = await apiClient.get(`${HEADLESS_BASE}/auth/session`);
|
|
32
48
|
return res.data;
|
|
@@ -411,4 +427,4 @@ export async function updateUserSupportStatus(userId, isSupportAgent) {
|
|
|
411
427
|
} catch (error) {
|
|
412
428
|
throw normaliseApiError(error, 'Auth.USER_SUPPORT_UPDATE_FAILED');
|
|
413
429
|
}
|
|
414
|
-
}
|
|
430
|
+
}
|
|
@@ -10,10 +10,14 @@ import {
|
|
|
10
10
|
CircularProgress,
|
|
11
11
|
Alert,
|
|
12
12
|
Typography,
|
|
13
|
+
Accordion,
|
|
14
|
+
AccordionSummary,
|
|
15
|
+
AccordionDetails,
|
|
13
16
|
} from '@mui/material';
|
|
14
17
|
import { useTranslation } from 'react-i18next';
|
|
15
18
|
// WICHTIG: Importiere den Context, um die bereits geladenen Daten zu nutzen
|
|
16
19
|
import { AuthContext } from '../auth/AuthContext';
|
|
20
|
+
import { fetchCookieStatement, fetchPrivacyStatement } from '../auth/authApi';
|
|
17
21
|
|
|
18
22
|
export function ProfileComponent({
|
|
19
23
|
onSubmit,
|
|
@@ -21,6 +25,9 @@ export function ProfileComponent({
|
|
|
21
25
|
showName = true,
|
|
22
26
|
showPrivacy = true,
|
|
23
27
|
showCookies = true,
|
|
28
|
+
showStatements = true,
|
|
29
|
+
privacyStatementText = null,
|
|
30
|
+
cookieStatementText = null,
|
|
24
31
|
}) {
|
|
25
32
|
const { t } = useTranslation();
|
|
26
33
|
|
|
@@ -39,6 +46,10 @@ export function ProfileComponent({
|
|
|
39
46
|
const [lastName, setLastName] = useState('');
|
|
40
47
|
const [acceptedPrivacy, setAcceptedPrivacy] = useState(false);
|
|
41
48
|
const [acceptedCookies, setAcceptedCookies] = useState(false);
|
|
49
|
+
const [privacyStatement, setPrivacyStatement] = useState(privacyStatementText || '');
|
|
50
|
+
const [cookieStatement, setCookieStatement] = useState(cookieStatementText || '');
|
|
51
|
+
const [loadingPrivacyStatement, setLoadingPrivacyStatement] = useState(false);
|
|
52
|
+
const [loadingCookieStatement, setLoadingCookieStatement] = useState(false);
|
|
42
53
|
|
|
43
54
|
// Synchronisiere Formular-Daten, sobald der User aus dem Context da ist
|
|
44
55
|
useEffect(() => {
|
|
@@ -52,6 +63,56 @@ export function ProfileComponent({
|
|
|
52
63
|
}
|
|
53
64
|
}, [user]);
|
|
54
65
|
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
if (privacyStatementText !== null && privacyStatementText !== undefined) {
|
|
68
|
+
setPrivacyStatement(String(privacyStatementText));
|
|
69
|
+
}
|
|
70
|
+
}, [privacyStatementText]);
|
|
71
|
+
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
if (cookieStatementText !== null && cookieStatementText !== undefined) {
|
|
74
|
+
setCookieStatement(String(cookieStatementText));
|
|
75
|
+
}
|
|
76
|
+
}, [cookieStatementText]);
|
|
77
|
+
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
let cancelled = false;
|
|
80
|
+
|
|
81
|
+
const loadStatements = async () => {
|
|
82
|
+
if (!showStatements) return;
|
|
83
|
+
|
|
84
|
+
if (showPrivacy && (privacyStatementText === null || privacyStatementText === undefined)) {
|
|
85
|
+
setLoadingPrivacyStatement(true);
|
|
86
|
+
try {
|
|
87
|
+
const text = await fetchPrivacyStatement();
|
|
88
|
+
if (!cancelled) setPrivacyStatement(text || '');
|
|
89
|
+
} catch (err) {
|
|
90
|
+
if (!cancelled) setPrivacyStatement('');
|
|
91
|
+
} finally {
|
|
92
|
+
if (!cancelled) setLoadingPrivacyStatement(false);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (showCookies && (cookieStatementText === null || cookieStatementText === undefined)) {
|
|
97
|
+
setLoadingCookieStatement(true);
|
|
98
|
+
try {
|
|
99
|
+
const text = await fetchCookieStatement();
|
|
100
|
+
if (!cancelled) setCookieStatement(text || '');
|
|
101
|
+
} catch (err) {
|
|
102
|
+
if (!cancelled) setCookieStatement('');
|
|
103
|
+
} finally {
|
|
104
|
+
if (!cancelled) setLoadingCookieStatement(false);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
loadStatements();
|
|
110
|
+
|
|
111
|
+
return () => {
|
|
112
|
+
cancelled = true;
|
|
113
|
+
};
|
|
114
|
+
}, [showStatements, showPrivacy, showCookies, privacyStatementText, cookieStatementText]);
|
|
115
|
+
|
|
55
116
|
const handleSubmit = async (event) => {
|
|
56
117
|
event.preventDefault();
|
|
57
118
|
if (!onSubmit) return;
|
|
@@ -61,10 +122,16 @@ export function ProfileComponent({
|
|
|
61
122
|
setSuccessKey(null);
|
|
62
123
|
|
|
63
124
|
const payload = {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
125
|
+
...(showName && {
|
|
126
|
+
first_name: firstName,
|
|
127
|
+
last_name: lastName,
|
|
128
|
+
}),
|
|
129
|
+
...(showPrivacy && {
|
|
130
|
+
accepted_privacy_statement: acceptedPrivacy,
|
|
131
|
+
}),
|
|
132
|
+
...(showCookies && {
|
|
133
|
+
accepted_convenience_cookies: acceptedCookies,
|
|
134
|
+
}),
|
|
68
135
|
};
|
|
69
136
|
|
|
70
137
|
try {
|
|
@@ -183,6 +250,52 @@ export function ProfileComponent({
|
|
|
183
250
|
/>
|
|
184
251
|
)}
|
|
185
252
|
</Stack>
|
|
253
|
+
|
|
254
|
+
{showStatements && (
|
|
255
|
+
<Stack spacing={1.25} sx={{ mt: 1.5 }}>
|
|
256
|
+
{showPrivacy && (
|
|
257
|
+
<Accordion disableGutters>
|
|
258
|
+
<AccordionSummary>
|
|
259
|
+
<Typography variant="body2" fontWeight={600}>
|
|
260
|
+
{t('Profile.PRIVACY_STATEMENT_TITLE', 'Privacy statement')}
|
|
261
|
+
</Typography>
|
|
262
|
+
</AccordionSummary>
|
|
263
|
+
<AccordionDetails>
|
|
264
|
+
{loadingPrivacyStatement ? (
|
|
265
|
+
<Typography variant="body2" color="text.secondary">
|
|
266
|
+
{t('Profile.STATEMENT_LOADING', 'Loading statement...')}
|
|
267
|
+
</Typography>
|
|
268
|
+
) : (
|
|
269
|
+
<Typography variant="body2" sx={{ whiteSpace: 'pre-wrap' }}>
|
|
270
|
+
{privacyStatement || t('Profile.PRIVACY_STATEMENT_EMPTY', 'Privacy statement is currently unavailable.')}
|
|
271
|
+
</Typography>
|
|
272
|
+
)}
|
|
273
|
+
</AccordionDetails>
|
|
274
|
+
</Accordion>
|
|
275
|
+
)}
|
|
276
|
+
|
|
277
|
+
{showCookies && (
|
|
278
|
+
<Accordion disableGutters>
|
|
279
|
+
<AccordionSummary>
|
|
280
|
+
<Typography variant="body2" fontWeight={600}>
|
|
281
|
+
{t('Profile.COOKIES_STATEMENT_TITLE', 'Cookie statement')}
|
|
282
|
+
</Typography>
|
|
283
|
+
</AccordionSummary>
|
|
284
|
+
<AccordionDetails>
|
|
285
|
+
{loadingCookieStatement ? (
|
|
286
|
+
<Typography variant="body2" color="text.secondary">
|
|
287
|
+
{t('Profile.STATEMENT_LOADING', 'Loading statement...')}
|
|
288
|
+
</Typography>
|
|
289
|
+
) : (
|
|
290
|
+
<Typography variant="body2" sx={{ whiteSpace: 'pre-wrap' }}>
|
|
291
|
+
{cookieStatement || t('Profile.COOKIES_STATEMENT_EMPTY', 'Cookie statement is currently unavailable.')}
|
|
292
|
+
</Typography>
|
|
293
|
+
)}
|
|
294
|
+
</AccordionDetails>
|
|
295
|
+
</Accordion>
|
|
296
|
+
)}
|
|
297
|
+
</Stack>
|
|
298
|
+
)}
|
|
186
299
|
</Box>
|
|
187
300
|
)}
|
|
188
301
|
|
|
@@ -34,7 +34,7 @@ export function UserInviteComponent() { // FIX: Removed apiUrl prop
|
|
|
34
34
|
};
|
|
35
35
|
|
|
36
36
|
return (
|
|
37
|
-
<Box sx={{ maxWidth: 600
|
|
37
|
+
<Box sx={{ maxWidth: 600 }}>
|
|
38
38
|
<Typography variant="h6" gutterBottom>
|
|
39
39
|
{t('Auth.INVITE_TITLE', 'Invite a new user')}
|
|
40
40
|
</Typography>
|
|
@@ -67,4 +67,4 @@ export function UserInviteComponent() { // FIX: Removed apiUrl prop
|
|
|
67
67
|
</Box>
|
|
68
68
|
</Box>
|
|
69
69
|
);
|
|
70
|
-
}
|
|
70
|
+
}
|