@micha.bigler/ui-core-micha 1.4.26 → 1.4.28

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.
@@ -25,7 +25,9 @@ export const AuthProvider = ({ children }) => {
25
25
  last_name: data.last_name,
26
26
  role: data.role,
27
27
  is_superuser: data.is_superuser,
28
- security_state: data.security_state, // Ensure this is passed if needed
28
+ security_state: data.security_state,
29
+ available_roles: data.available_roles,
30
+ ui_permissions: data.ui_permissions,
29
31
  });
30
32
  }
31
33
  }
@@ -315,3 +315,64 @@ export async function loginWithRecoveryPassword(email, password, token) {
315
315
  const user = await fetchCurrentUser();
316
316
  return { user, needsMfa: false };
317
317
  }
318
+ /**
319
+ * Ruft eine Liste von Benutzern ab (oft mit Pagination/Search).
320
+ * Backend: GET /api/users/
321
+ */
322
+ export async function fetchUsersList(params = {}) {
323
+ try {
324
+ // params kann { page: 1, search: "...", ordering: "email" } enthalten
325
+ const res = await apiClient.get(`${USERS_BASE}/`, { params });
326
+ return res.data;
327
+ }
328
+ catch (error) {
329
+ throw normaliseApiError(error, 'Auth.USER_LIST_FAILED');
330
+ }
331
+ }
332
+ /**
333
+ * Löscht einen Benutzer.
334
+ * Backend: DELETE /api/users/{id}/
335
+ */
336
+ export async function deleteUser(userId) {
337
+ try {
338
+ await apiClient.delete(`${USERS_BASE}/${userId}/`);
339
+ }
340
+ catch (error) {
341
+ throw normaliseApiError(error, 'Auth.USER_DELETE_FAILED');
342
+ }
343
+ }
344
+ /**
345
+ * Aktualisiert die Rolle eines Benutzers über die Custom Action.
346
+ * Backend: PATCH /api/users/{id}/update-role/
347
+ */
348
+ export async function updateUserRole(userId, newRole) {
349
+ try {
350
+ const res = await apiClient.patch(`${USERS_BASE}/${userId}/update-role/`, {
351
+ role: newRole,
352
+ });
353
+ return res.data;
354
+ }
355
+ catch (error) {
356
+ throw normaliseApiError(error, 'Auth.USER_ROLE_UPDATE_FAILED');
357
+ }
358
+ }
359
+ /**
360
+ * Aktualisiert den Support-Status (z.B. is_support_agent Flag).
361
+ * Backend: PATCH /api/users/{id}/ (Standard DRF Update mit Nested Profile)
362
+ */
363
+ export async function updateUserSupportStatus(userId, isSupportAgent) {
364
+ try {
365
+ // Wir gehen davon aus, dass dein Serializer "profile" nested akzeptiert
366
+ // (siehe BaseUserSerializer.update Logik im Backend)
367
+ const payload = {
368
+ profile: {
369
+ is_support_agent: isSupportAgent,
370
+ },
371
+ };
372
+ const res = await apiClient.patch(`${USERS_BASE}/${userId}/`, payload);
373
+ return res.data;
374
+ }
375
+ catch (error) {
376
+ throw normaliseApiError(error, 'Auth.USER_SUPPORT_UPDATE_FAILED');
377
+ }
378
+ }
@@ -3,7 +3,7 @@ import React, { useState } from 'react';
3
3
  import { Box, TextField, Button, Typography, Alert, CircularProgress } from '@mui/material';
4
4
  import { requestInviteWithCode } from '../auth/authApi';
5
5
  import { useTranslation } from 'react-i18next';
6
- export function UserInviteComponent({ apiUrl = '/api/users/' }) {
6
+ export function UserInviteComponent() {
7
7
  const { t } = useTranslation();
8
8
  const [inviteEmail, setInviteEmail] = useState('');
9
9
  const [message, setMessage] = useState('');
@@ -16,15 +16,16 @@ export function UserInviteComponent({ apiUrl = '/api/users/' }) {
16
16
  return;
17
17
  setLoading(true);
18
18
  try {
19
- // API Call via authApi
20
- const data = await requestInviteWithCode(inviteEmail, apiUrl);
19
+ // FIX: 2nd parameter is accessCode. For admin invites without code, we pass null.
20
+ // Previously, 'apiUrl' was passed here incorrectly.
21
+ const data = await requestInviteWithCode(inviteEmail, null);
21
22
  setInviteEmail('');
22
23
  setMessage(data.detail || t('Auth.INVITE_SENT_SUCCESS', 'Invitation sent.'));
23
24
  }
24
25
  catch (err) {
25
- // err.message enthält dank authApi bereits den normalisierten Text oder Code
26
+ // err.message contains normalized text or code from authApi
27
+ // eslint-disable-next-line no-console
26
28
  console.error('Error inviting user:', err);
27
- // Fallback Text, falls der Key nicht übersetzt ist
28
29
  setError(t(err.code) || err.message || t('Auth.INVITE_FAILED'));
29
30
  }
30
31
  finally {
@@ -1,11 +1,11 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import React, { useState, useEffect } from 'react';
3
- import { Box, Typography, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper, FormControl, InputLabel, Select, MenuItem, Button, IconButton, Tooltip, CircularProgress, Alert } from '@mui/material';
3
+ import { Box, Typography, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper, FormControl, Select, MenuItem, Button, IconButton, Tooltip, CircularProgress, Alert } from '@mui/material';
4
4
  import DeleteIcon from '@mui/icons-material/Delete';
5
5
  import { useTranslation } from 'react-i18next';
6
6
  import { fetchUsersList, deleteUser, updateUserRole, updateUserSupportStatus } from '../auth/authApi';
7
7
  const DEFAULT_ROLES = ['none', 'student', 'teacher', 'admin'];
8
- export function UserListComponent({ roles = DEFAULT_ROLES, apiUrl = '/api/users/', currentUser }) {
8
+ export function UserListComponent({ roles = DEFAULT_ROLES, currentUser }) {
9
9
  const { t } = useTranslation();
10
10
  const [users, setUsers] = useState([]);
11
11
  const [loading, setLoading] = useState(true);
@@ -14,11 +14,12 @@ export function UserListComponent({ roles = DEFAULT_ROLES, apiUrl = '/api/users/
14
14
  setLoading(true);
15
15
  setError(null);
16
16
  try {
17
- const data = await fetchUsersList(apiUrl);
17
+ // FIX: Removed apiUrl parameter.
18
+ // fetchUsersList uses USERS_BASE from authConfig internally.
19
+ const data = await fetchUsersList();
18
20
  setUsers(data);
19
21
  }
20
22
  catch (err) {
21
- // err.code ist dank normaliseApiError verfügbar
22
23
  setError(err.code || 'Auth.USER_LIST_FAILED');
23
24
  }
24
25
  finally {
@@ -28,13 +29,13 @@ export function UserListComponent({ roles = DEFAULT_ROLES, apiUrl = '/api/users/
28
29
  useEffect(() => {
29
30
  loadUsers();
30
31
  // eslint-disable-next-line react-hooks/exhaustive-deps
31
- }, [apiUrl]);
32
+ }, []); // Dependency on apiUrl removed
32
33
  const handleDelete = async (userId) => {
33
34
  if (!window.confirm(t('UserList.DELETE_CONFIRM', 'Are you sure you want to delete this user?')))
34
35
  return;
35
36
  try {
36
- await deleteUser(userId, apiUrl);
37
- // Optimistic update oder reload:
37
+ // FIX: Removed apiUrl parameter.
38
+ await deleteUser(userId);
38
39
  setUsers(prev => prev.filter(u => u.id !== userId));
39
40
  }
40
41
  catch (err) {
@@ -43,8 +44,8 @@ export function UserListComponent({ roles = DEFAULT_ROLES, apiUrl = '/api/users/
43
44
  };
44
45
  const handleChangeRole = async (userId, newRole) => {
45
46
  try {
46
- await updateUserRole(userId, newRole, apiUrl);
47
- // Reload list to ensure consistency or update local state
47
+ // FIX: Removed apiUrl parameter.
48
+ await updateUserRole(userId, newRole);
48
49
  loadUsers();
49
50
  }
50
51
  catch (err) {
@@ -53,7 +54,8 @@ export function UserListComponent({ roles = DEFAULT_ROLES, apiUrl = '/api/users/
53
54
  };
54
55
  const handleToggleSupporter = async (userId, newValue) => {
55
56
  try {
56
- await updateUserSupportStatus(userId, newValue, apiUrl);
57
+ // FIX: Removed apiUrl parameter.
58
+ await updateUserSupportStatus(userId, newValue);
57
59
  loadUsers();
58
60
  }
59
61
  catch (err) {
@@ -69,7 +71,6 @@ export function UserListComponent({ roles = DEFAULT_ROLES, apiUrl = '/api/users/
69
71
  const targetRole = targetUser.role || 'none';
70
72
  if (myRole === 'admin')
71
73
  return true;
72
- // Beispiel Logik: Lehrer dürfen Schüler bearbeiten
73
74
  if (myRole === 'teacher') {
74
75
  if (targetUser.id === currentUser.id)
75
76
  return false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@micha.bigler/ui-core-micha",
3
- "version": "1.4.26",
3
+ "version": "1.4.28",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/index.js",
6
6
  "private": false,
@@ -37,7 +37,9 @@ export const AuthProvider = ({ children }) => {
37
37
  last_name: data.last_name,
38
38
  role: data.role,
39
39
  is_superuser: data.is_superuser,
40
- security_state: data.security_state, // Ensure this is passed if needed
40
+ security_state: data.security_state,
41
+ available_roles: data.available_roles,
42
+ ui_permissions: data.ui_permissions,
41
43
  });
42
44
  }
43
45
  } catch (err) {
@@ -328,4 +328,64 @@ export async function loginWithRecoveryPassword(email, password, token) {
328
328
  }
329
329
  const user = await fetchCurrentUser();
330
330
  return { user, needsMfa: false };
331
+ }
332
+ /**
333
+ * Ruft eine Liste von Benutzern ab (oft mit Pagination/Search).
334
+ * Backend: GET /api/users/
335
+ */
336
+ export async function fetchUsersList(params = {}) {
337
+ try {
338
+ // params kann { page: 1, search: "...", ordering: "email" } enthalten
339
+ const res = await apiClient.get(`${USERS_BASE}/`, { params });
340
+ return res.data;
341
+ } catch (error) {
342
+ throw normaliseApiError(error, 'Auth.USER_LIST_FAILED');
343
+ }
344
+ }
345
+
346
+ /**
347
+ * Löscht einen Benutzer.
348
+ * Backend: DELETE /api/users/{id}/
349
+ */
350
+ export async function deleteUser(userId) {
351
+ try {
352
+ await apiClient.delete(`${USERS_BASE}/${userId}/`);
353
+ } catch (error) {
354
+ throw normaliseApiError(error, 'Auth.USER_DELETE_FAILED');
355
+ }
356
+ }
357
+
358
+ /**
359
+ * Aktualisiert die Rolle eines Benutzers über die Custom Action.
360
+ * Backend: PATCH /api/users/{id}/update-role/
361
+ */
362
+ export async function updateUserRole(userId, newRole) {
363
+ try {
364
+ const res = await apiClient.patch(`${USERS_BASE}/${userId}/update-role/`, {
365
+ role: newRole,
366
+ });
367
+ return res.data;
368
+ } catch (error) {
369
+ throw normaliseApiError(error, 'Auth.USER_ROLE_UPDATE_FAILED');
370
+ }
371
+ }
372
+
373
+ /**
374
+ * Aktualisiert den Support-Status (z.B. is_support_agent Flag).
375
+ * Backend: PATCH /api/users/{id}/ (Standard DRF Update mit Nested Profile)
376
+ */
377
+ export async function updateUserSupportStatus(userId, isSupportAgent) {
378
+ try {
379
+ // Wir gehen davon aus, dass dein Serializer "profile" nested akzeptiert
380
+ // (siehe BaseUserSerializer.update Logik im Backend)
381
+ const payload = {
382
+ profile: {
383
+ is_support_agent: isSupportAgent,
384
+ },
385
+ };
386
+ const res = await apiClient.patch(`${USERS_BASE}/${userId}/`, payload);
387
+ return res.data;
388
+ } catch (error) {
389
+ throw normaliseApiError(error, 'Auth.USER_SUPPORT_UPDATE_FAILED');
390
+ }
331
391
  }
@@ -3,7 +3,7 @@ import { Box, TextField, Button, Typography, Alert, CircularProgress } from '@mu
3
3
  import { requestInviteWithCode } from '../auth/authApi';
4
4
  import { useTranslation } from 'react-i18next';
5
5
 
6
- export function UserInviteComponent({ apiUrl = '/api/users/' }) {
6
+ export function UserInviteComponent() { // FIX: Removed apiUrl prop
7
7
  const { t } = useTranslation();
8
8
  const [inviteEmail, setInviteEmail] = useState('');
9
9
  const [message, setMessage] = useState('');
@@ -17,15 +17,16 @@ export function UserInviteComponent({ apiUrl = '/api/users/' }) {
17
17
 
18
18
  setLoading(true);
19
19
  try {
20
- // API Call via authApi
21
- const data = await requestInviteWithCode(inviteEmail, apiUrl);
20
+ // FIX: 2nd parameter is accessCode. For admin invites without code, we pass null.
21
+ // Previously, 'apiUrl' was passed here incorrectly.
22
+ const data = await requestInviteWithCode(inviteEmail, null);
22
23
 
23
24
  setInviteEmail('');
24
25
  setMessage(data.detail || t('Auth.INVITE_SENT_SUCCESS', 'Invitation sent.'));
25
26
  } catch (err) {
26
- // err.message enthält dank authApi bereits den normalisierten Text oder Code
27
+ // err.message contains normalized text or code from authApi
28
+ // eslint-disable-next-line no-console
27
29
  console.error('Error inviting user:', err);
28
- // Fallback Text, falls der Key nicht übersetzt ist
29
30
  setError(t(err.code) || err.message || t('Auth.INVITE_FAILED'));
30
31
  } finally {
31
32
  setLoading(false);
@@ -1,18 +1,17 @@
1
1
  import React, { useState, useEffect } from 'react';
2
2
  import {
3
3
  Box, Typography, Table, TableBody, TableCell, TableContainer,
4
- TableHead, TableRow, Paper, FormControl, InputLabel, Select,
4
+ TableHead, TableRow, Paper, FormControl, Select,
5
5
  MenuItem, Button, IconButton, Tooltip, CircularProgress, Alert
6
6
  } from '@mui/material';
7
7
  import DeleteIcon from '@mui/icons-material/Delete';
8
8
  import { useTranslation } from 'react-i18next';
9
- import { fetchUsersList, deleteUser, updateUserRole, updateUserSupportStatus } from '../auth/authApi';
9
+ import { fetchUsersList, deleteUser, updateUserRole, updateUserSupportStatus } from '../auth/authApi';
10
10
 
11
11
  const DEFAULT_ROLES = ['none', 'student', 'teacher', 'admin'];
12
12
 
13
13
  export function UserListComponent({
14
14
  roles = DEFAULT_ROLES,
15
- apiUrl = '/api/users/',
16
15
  currentUser
17
16
  }) {
18
17
  const { t } = useTranslation();
@@ -24,10 +23,11 @@ export function UserListComponent({
24
23
  setLoading(true);
25
24
  setError(null);
26
25
  try {
27
- const data = await fetchUsersList(apiUrl);
26
+ // FIX: Removed apiUrl parameter.
27
+ // fetchUsersList uses USERS_BASE from authConfig internally.
28
+ const data = await fetchUsersList();
28
29
  setUsers(data);
29
30
  } catch (err) {
30
- // err.code ist dank normaliseApiError verfügbar
31
31
  setError(err.code || 'Auth.USER_LIST_FAILED');
32
32
  } finally {
33
33
  setLoading(false);
@@ -37,13 +37,13 @@ export function UserListComponent({
37
37
  useEffect(() => {
38
38
  loadUsers();
39
39
  // eslint-disable-next-line react-hooks/exhaustive-deps
40
- }, [apiUrl]);
40
+ }, []); // Dependency on apiUrl removed
41
41
 
42
42
  const handleDelete = async (userId) => {
43
43
  if (!window.confirm(t('UserList.DELETE_CONFIRM', 'Are you sure you want to delete this user?'))) return;
44
44
  try {
45
- await deleteUser(userId, apiUrl);
46
- // Optimistic update oder reload:
45
+ // FIX: Removed apiUrl parameter.
46
+ await deleteUser(userId);
47
47
  setUsers(prev => prev.filter(u => u.id !== userId));
48
48
  } catch (err) {
49
49
  alert(t(err.code || 'Auth.USER_DELETE_FAILED'));
@@ -52,8 +52,8 @@ export function UserListComponent({
52
52
 
53
53
  const handleChangeRole = async (userId, newRole) => {
54
54
  try {
55
- await updateUserRole(userId, newRole, apiUrl);
56
- // Reload list to ensure consistency or update local state
55
+ // FIX: Removed apiUrl parameter.
56
+ await updateUserRole(userId, newRole);
57
57
  loadUsers();
58
58
  } catch (err) {
59
59
  alert(t(err.code || 'Auth.USER_ROLE_UPDATE_FAILED'));
@@ -62,7 +62,8 @@ export function UserListComponent({
62
62
 
63
63
  const handleToggleSupporter = async (userId, newValue) => {
64
64
  try {
65
- await updateUserSupportStatus(userId, newValue, apiUrl);
65
+ // FIX: Removed apiUrl parameter.
66
+ await updateUserSupportStatus(userId, newValue);
66
67
  loadUsers();
67
68
  } catch (err) {
68
69
  alert(t(err.code || 'Auth.USER_UPDATE_FAILED'));
@@ -78,7 +79,6 @@ export function UserListComponent({
78
79
 
79
80
  if (myRole === 'admin') return true;
80
81
 
81
- // Beispiel Logik: Lehrer dürfen Schüler bearbeiten
82
82
  if (myRole === 'teacher') {
83
83
  if (targetUser.id === currentUser.id) return false;
84
84
  if (['teacher', 'admin', 'supervisor'].includes(targetRole)) return false;