@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.
@@ -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
- // Map data to ensure consistent structure
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
+ };
@@ -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
+ }
@@ -118,7 +118,7 @@ export function AccessCodeManager() {
118
118
  }
119
119
 
120
120
  return (
121
- <Box sx={{ mt: 2 }}>
121
+ <Box>
122
122
  {errorKey && (
123
123
  <Alert severity="error" sx={{ mb: 2 }}>
124
124
  {t(errorKey)}
@@ -117,7 +117,7 @@ export function BulkInviteCsvTab({
117
117
  };
118
118
 
119
119
  return (
120
- <Box sx={{ mt: 1 }}>
120
+ <Box>
121
121
  <Typography variant="h6" gutterBottom>
122
122
  {t('Account.BULK_INVITE_TITLE', 'Bulk Invite via CSV')}
123
123
  </Typography>
@@ -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
- first_name: firstName,
65
- last_name: lastName,
66
- accepted_privacy_statement: acceptedPrivacy,
67
- accepted_convenience_cookies: acceptedCookies,
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, mt: 2 }}>
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
+ }