diff --git a/src/App.tsx b/src/App.tsx
index 091da5f..e10dc56 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -3,9 +3,11 @@ import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import Login from './pages/Login';
import Register from './pages/Register';
-import { AuthProvider } from './auth/auth-provider';
+import { AuthProvider } from './auth/authProvider';
import { ProtectedRoute } from './auth/ProtectedRoute';
import Home from './pages/Home';
+import Dateien from './pages/Dateien/Dateien';
+import Mitglieder from './pages/Mitglieder/Mitglieder';
function App() {
return (
@@ -19,7 +21,10 @@ function App() {
- }>
+ }>
+ } />
+ } />
+
diff --git a/src/auth/auth-config.ts b/src/auth/authConfig.ts
similarity index 100%
rename from src/auth/auth-config.ts
rename to src/auth/authConfig.ts
diff --git a/src/auth/auth-provider.tsx b/src/auth/authProvider.tsx
similarity index 98%
rename from src/auth/auth-provider.tsx
rename to src/auth/authProvider.tsx
index d8a99d7..21e9754 100644
--- a/src/auth/auth-provider.tsx
+++ b/src/auth/authProvider.tsx
@@ -4,7 +4,7 @@ import {
PublicClientApplication,
InteractionStatus
} from "@azure/msal-browser";
- import { msalConfig } from "./auth-config";
+ import { msalConfig } from "./authConfig";
import { MsalProvider } from "@azure/msal-react";
import { ReactNode, useEffect, useState } from "react";
diff --git a/src/components/Dateien/DateienItem.module.css b/src/components/Dateien/DateienItem.module.css
new file mode 100644
index 0000000..2e69493
--- /dev/null
+++ b/src/components/Dateien/DateienItem.module.css
@@ -0,0 +1,153 @@
+.fileItem {
+ display: flex;
+ align-items: center;
+ height: 60px;
+ padding: 0px 16px;
+ justify-content: space-between;
+ color: var(--Grayscale-Black, #24262B);
+ transition: background-color 0.2s ease;
+}
+
+.fileItem:hover {
+ background-color: #f9f9f9;
+}
+
+/* Column layout matching the header structure */
+.fileName {
+ flex: 3;
+ display: flex;
+ align-items: center;
+ overflow: hidden;
+ font-weight: 500;
+ color: #333;
+ padding-right: 8px;
+}
+
+.fileName span {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ margin-left: 12px;
+}
+
+.fileType {
+ flex: 1;
+ font-size: 14px;
+ color: #666;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+}
+
+.fileSize {
+ flex: 1;
+ font-size: 14px;
+ color: #666;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+}
+
+.fileDateWithActions {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ width: 100%;
+}
+
+.fileDate {
+ flex: 1.5;
+ font-size: 14px;
+ color: #666;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+}
+
+.icon {
+ font-size: 18px;
+ color: #757575;
+ flex-shrink: 0;
+}
+
+.actionButtons {
+ flex: 1;
+ display: flex;
+ gap: 4px;
+ justify-content: flex-end;
+}
+
+.downloadButton,
+.deleteButton {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 4px;
+ padding: 6px;
+ border: none;
+ border-radius: 4px;
+ background-color: transparent;
+ color: var(--text-color-secondary, #666);
+ cursor: pointer;
+ transition: all 0.2s ease;
+ min-width: 32px;
+}
+
+.downloadButton:hover:not(:disabled),
+.deleteButton:hover:not(:disabled) {
+ background-color: var(--background-color-hover, #e8e8e8);
+ color: var(--text-color-primary, #333);
+}
+
+.deleteButton:hover:not(:disabled) {
+ color: #dc2626;
+}
+
+.deleteButton.confirm {
+ background-color: #fee2e2;
+ color: #dc2626;
+}
+
+.deleteButton.confirm:hover:not(:disabled) {
+ background-color: #fecaca;
+}
+
+.downloadButton:disabled,
+.deleteButton:disabled {
+ cursor: not-allowed;
+ opacity: 0.7;
+}
+
+.actionIcon {
+ font-size: 16px;
+ flex-shrink: 0;
+}
+
+.downloadButton.downloading,
+.deleteButton.deleting {
+ background-color: var(--background-color-light, #f5f5f5);
+}
+
+.actionText {
+ font-size: 12px;
+ color: var(--text-color-secondary, #666);
+ animation: pulse 1.5s infinite;
+ white-space: nowrap;
+}
+
+.deleteButton.confirm .actionText {
+ color: #dc2626;
+ animation: none;
+}
+
+@keyframes pulse {
+ 0% {
+ opacity: 0.6;
+ }
+ 50% {
+ opacity: 1;
+ }
+ 100% {
+ opacity: 0.6;
+ }
+}
diff --git a/src/components/Dateien/DateienItem.tsx b/src/components/Dateien/DateienItem.tsx
new file mode 100644
index 0000000..ee2d293
--- /dev/null
+++ b/src/components/Dateien/DateienItem.tsx
@@ -0,0 +1,127 @@
+import { FaFile, FaDownload, FaTrash } from "react-icons/fa";
+import styles from "./DateienItem.module.css";
+import { useState } from "react";
+import { useFileOperations } from "../../hooks/useFiles";
+
+type DateienItemProps = {
+ file: {
+ id: number;
+ file_name: string;
+ action: string;
+ created_at: string;
+ size?: number;
+ };
+ onDelete?: () => void;
+};
+
+/**
+ * Formats a file size in bytes to a human-readable string (KB, MB, etc.)
+ */
+const formatFileSize = (bytes?: number): string => {
+ if (bytes === undefined || bytes === null) return 'Unbekannte Größe';
+
+ if (bytes === 0) return '0 Bytes';
+
+ const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
+ const i = Math.floor(Math.log(bytes) / Math.log(1024));
+
+ if (i === 0) return `${bytes} ${sizes[i]}`;
+
+ return `${(bytes / Math.pow(1024, i)).toFixed(1)} ${sizes[i]}`;
+};
+
+const DateienItem = ({ file, onDelete }: DateienItemProps) => {
+ const { downloadingFiles, deletingFiles, handleFileDownload, handleFileDelete } = useFileOperations();
+ const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
+ const isDownloading = downloadingFiles.has(file.id);
+ const isDeleting = deletingFiles.has(file.id);
+
+ // Format the date properly
+ const formatDate = (dateString: string) => {
+ try {
+ const date = new Date(dateString);
+ // Check if date is valid
+ if (isNaN(date.getTime())) {
+ return 'Unbekanntes Datum';
+ }
+
+ return date.toLocaleDateString('de-DE', {
+ day: '2-digit',
+ month: '2-digit',
+ year: 'numeric'
+ });
+ } catch (e) {
+ console.error('Error formatting date:', e);
+ return 'Unbekanntes Datum';
+ }
+ };
+
+ const handleDeleteClick = async () => {
+ if (showDeleteConfirm) {
+ const success = await handleFileDelete(file.id);
+ if (success && onDelete) {
+ onDelete();
+ }
+ setShowDeleteConfirm(false);
+ } else {
+ setShowDeleteConfirm(true);
+ }
+ };
+
+ const handleCancelDelete = () => {
+ setShowDeleteConfirm(false);
+ };
+
+ return (
+
+ {/* 1st column: Name with icon */}
+
+
+ {file.file_name}
+
+
+ {/* 2nd column: Type */}
+
+ {file.action}
+
+
+ {/* 3rd column: Size */}
+
+ {formatFileSize(file.size)}
+
+
+ {/* 4th column: Date and action buttons */}
+
+
+ {formatDate(file.created_at)}
+
+
+
+
+
+
+
+
+ );
+};
+
+export default DateienItem;
+
diff --git a/src/components/Mitglieder/MitgliederItem.module.css b/src/components/Mitglieder/MitgliederItem.module.css
new file mode 100644
index 0000000..ecc7396
--- /dev/null
+++ b/src/components/Mitglieder/MitgliederItem.module.css
@@ -0,0 +1,53 @@
+.memberItem {
+ display: flex;
+ align-items: center;
+ height: 70px;
+ padding: 0px 16px;
+ justify-content: space-between;
+ color: var(--Grayscale-Black, #24262B);
+ }
+
+ .userProfile {
+ margin-right: 12px;
+ }
+
+ .profileIcon {
+ font-size: 36px;
+ color: var(--Grayscale-Gray, #E9E9E9);
+ }
+
+ .userInfo {
+ display: grid;
+ grid-template-columns: 200px 250px 100px;
+ gap: 16px;
+ flex-grow: 1;
+ align-items: center;
+ }
+
+ .userName {
+ margin: 0;
+ font-size: 14px;
+ }
+
+ .userEmail,
+ .userRole {
+ margin: 0;
+ font-size: 12px;
+ font-weight: light;
+ }
+
+ .actions {
+ display: flex;
+
+ align-items: center;
+ justify-content: center;
+ }
+
+ .editBtn:hover {
+ color: var(--Brand-Green-Green, #3A8088);
+ }
+
+ .deleteBtn:hover {
+ color: var(--Brand-Green-Green, #3A8088);
+ }
+
\ No newline at end of file
diff --git a/src/components/Mitglieder/MitgliederItem.tsx b/src/components/Mitglieder/MitgliederItem.tsx
new file mode 100644
index 0000000..b41d6aa
--- /dev/null
+++ b/src/components/Mitglieder/MitgliederItem.tsx
@@ -0,0 +1,32 @@
+import { FaUserCircle } from "react-icons/fa";
+import styles from "./MitgliederItem.module.css";
+import MitgliederItemDelete from "./MitgliederItemDelete/MitgliederItemDelete";
+import MitgliederItemEdit from "./MitgliederItemEdit/MitgliederItemEdit";
+import { User } from "../../hooks/useUsers";
+
+type MitgliederItemProps = {
+ user: User;
+ refetchUsers: () => Promise;
+ totalUsers: number;
+ };
+
+ const MitgliederItem = ({ user, refetchUsers, totalUsers }: MitgliederItemProps) => {
+ return (
+
+
+
+
+
+ {user.fullName || user.username}
+ {user.email}
+ {user.privilege}
+
+
+
+
+
+
+ );
+ };
+
+ export default MitgliederItem;
\ No newline at end of file
diff --git a/src/components/Mitglieder/MitgliederItemDelete/DeletePopUp.module.css b/src/components/Mitglieder/MitgliederItemDelete/DeletePopUp.module.css
new file mode 100644
index 0000000..b529250
--- /dev/null
+++ b/src/components/Mitglieder/MitgliederItemDelete/DeletePopUp.module.css
@@ -0,0 +1,92 @@
+.popupHeader {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-top:10px;
+ margin-left: 10px;
+ margin-right: 10px;
+ margin-bottom: 5px;
+ width: 300px;
+ height: 20px;
+ color: gray;
+ font-size: 10px;
+
+}
+
+.closeButton {
+ display: flex;
+ align-items: center;
+ background: none;
+ border: none;
+ cursor: pointer;
+ width: 20px; /* Increased button size */
+ height: 20px;
+ line-height: 0;
+ padding: 0;
+ margin:0;
+
+}
+
+.closeIcon {
+ width: 100%; /* Make the icon fill the button's width */
+ height: 100%;
+ padding: 0;
+ margin:0;
+ display: block;
+}
+
+.horizontalLineLight {
+ width: 100%;
+ background-color: #F1F1F1;
+ height: 1px;
+}
+
+.userInfo {
+ display: grid;
+ grid-column: 1;
+ padding: none;
+ margin: none;
+ align-items: center;
+ justify-content: center; /* Center horizontally */
+}
+
+.userInfoParagraph{
+ display: flex;
+ color: gray;
+ font-size: 10px;
+ margin:0px;
+ justify-content: center;
+ gap: 10px;
+}
+.userInfo h2{
+ display: flex;
+ justify-content: center;
+ margin: 0;
+ margin-top:10px;
+ justify-content: center;
+}
+
+.submitButtonDiv {
+ margin: 10px;
+ display: flex; /* Use Flexbox */
+ justify-content: center; /* Center horizontally */
+ align-items: center;
+}
+.submitButton {
+ background: none;
+ border: 1px solid black;
+ border-radius: 5px;
+ padding: 5px 20px; /* Optional: Adds some padding for better button size */
+ cursor: pointer;
+ width: 80%
+}
+
+.submitButton:hover {
+ background: rgb(168, 18, 18);
+ border: 1px solid rgb(168, 18, 18);
+ border-radius: 5px;
+ padding: 5px 20px; /* Optional: Adds some padding for better button size */
+ cursor: pointer;
+ color: white;
+ transition: 0.2s;
+}
\ No newline at end of file
diff --git a/src/components/Mitglieder/MitgliederItemDelete/DeletePopUp.tsx b/src/components/Mitglieder/MitgliederItemDelete/DeletePopUp.tsx
new file mode 100644
index 0000000..925ad7d
--- /dev/null
+++ b/src/components/Mitglieder/MitgliederItemDelete/DeletePopUp.tsx
@@ -0,0 +1,88 @@
+import { IoCloseOutline } from "react-icons/io5";
+import styles from "./DeletePopUp.module.css";
+import { User, useOrgUsers } from "../../../hooks/useUsers";
+import { useState } from "react";
+
+type DeletePopUpProps = {
+ closePopup: () => void;
+ refetchUsers: () => Promise;
+ user: User;
+};
+
+const DeletePopUp = ({ closePopup, refetchUsers, user }: DeletePopUpProps) => {
+ const { deleteUser } = useOrgUsers();
+ const [isDeleting, setIsDeleting] = useState(false);
+ const [error, setError] = useState(null);
+
+ const handleDelete = async () => {
+ setIsDeleting(true);
+ setError(null);
+
+ try {
+ await deleteUser(user.id);
+ // Refresh the users list and close the popup
+ await refetchUsers();
+ closePopup();
+ } catch (error: any) {
+ setError(error.message || "Failed to delete user");
+ setIsDeleting(false);
+ }
+ };
+
+ return (
+
+
+
+
+ {error &&
{error}
}
+
+
{user.fullName || user.username}
+
+
Email: {user.email}
+
Privilege: {user.privilege}
+
+
+
+
+
+
+
+
+
+ )
+}
+
+const popupStyles: { overlay: React.CSSProperties; popup: React.CSSProperties } = {
+ overlay: {
+ position: "fixed",
+ top: 0,
+ left: 0,
+ right: 0,
+ bottom: 0,
+ backgroundColor: "rgba(0,0,0,0.5)",
+ display: "flex",
+ justifyContent: "center",
+ alignItems: "center",
+
+ },
+ popup: {
+ backgroundColor: "#fff",
+ borderRadius: "8px",
+ boxShadow: "0 5px 15px rgba(0,0,0,0.3)",
+ position: "relative"
+ }
+};
+
+export default DeletePopUp;
\ No newline at end of file
diff --git a/src/components/Mitglieder/MitgliederItemDelete/MitgliederItemDelete.module.css b/src/components/Mitglieder/MitgliederItemDelete/MitgliederItemDelete.module.css
new file mode 100644
index 0000000..6a4c4bf
--- /dev/null
+++ b/src/components/Mitglieder/MitgliederItemDelete/MitgliederItemDelete.module.css
@@ -0,0 +1,33 @@
+.deleteBtn {
+ background: none;
+ border: none;
+ cursor: pointer;
+ font-size: 18px;
+ color: #444;
+ height: 100;
+}
+
+.deleteBtn:hover {
+ color: var(--Brand-Green-Green, #3A8088);
+}
+
+.deleteBtn.disabled {
+ cursor: not-allowed;
+ opacity: 0.5;
+ background: none;
+ border: none;
+ cursor: pointer;
+ font-size: 18px;
+ color: #444;
+}
+
+.deleteBtn.disabled:hover {
+ color: #444;
+}
+
+.deleteBtnContainer {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+}
diff --git a/src/components/Mitglieder/MitgliederItemDelete/MitgliederItemDelete.tsx b/src/components/Mitglieder/MitgliederItemDelete/MitgliederItemDelete.tsx
new file mode 100644
index 0000000..4bb686f
--- /dev/null
+++ b/src/components/Mitglieder/MitgliederItemDelete/MitgliederItemDelete.tsx
@@ -0,0 +1,37 @@
+import { FaTrash } from "react-icons/fa";
+import styles from "./MitgliederItemDelete.module.css";
+import { useState } from "react";
+import DeletePopUp from "./DeletePopUp";
+import { User } from "../../../hooks/useUsers";
+
+type MitgliederItemDeleteProps = {
+ user: User;
+ refetchUsers: () => Promise;
+ isDisabled?: boolean;
+};
+
+const MitgliederItemDelete = ({ user, refetchUsers, isDisabled = false }: MitgliederItemDeleteProps) => {
+ const [deleteWindow, setDeleteWindow] = useState(false);
+
+ return (
+
+
+
+ {deleteWindow && (
+ setDeleteWindow(false)}
+ refetchUsers={refetchUsers}
+ />
+ )}
+
+ );
+ };
+
+ export default MitgliederItemDelete;
\ No newline at end of file
diff --git a/src/components/Mitglieder/MitgliederItemEdit/EditPopUp.module.css b/src/components/Mitglieder/MitgliederItemEdit/EditPopUp.module.css
new file mode 100644
index 0000000..0faf35d
--- /dev/null
+++ b/src/components/Mitglieder/MitgliederItemEdit/EditPopUp.module.css
@@ -0,0 +1,89 @@
+.popupHeader {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 15px 20px;
+}
+
+.popupHeader p {
+ font-size: 1.2rem;
+ font-weight: 600;
+ margin: 0;
+}
+
+.closeButton {
+ background: none;
+ border: none;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.closeIcon {
+ font-size: 1.5rem;
+ color: #666;
+}
+
+.horizontalLineLight {
+ height: 1px;
+ background-color: #e0e0e0;
+ width: 100%;
+ margin: 0;
+}
+
+.form {
+ box-sizing: border-box;
+ max-width: 100%;
+ padding: 20px;
+}
+
+.formGroup {
+ margin-bottom: 15px;
+ box-sizing: border-box;
+}
+
+.formGroup label {
+ display: block;
+ margin-bottom: 5px;
+ font-weight: 500;
+}
+
+.formGroup input,
+.formGroup select {
+ width: 100%;
+ padding: 8px 12px;
+ border: 1px solid #ddd;
+ border-radius: 4px;
+ font-size: 1rem;
+ box-sizing: border-box;
+}
+
+.submitButtonDiv {
+ display: flex;
+ justify-content: flex-end;
+ padding: 15px 0 10px;
+}
+
+.submitButton {
+ background-color: #4c9aff;
+ color: white;
+ border: none;
+ border-radius: 4px;
+ padding: 8px 16px;
+ font-size: 1rem;
+ cursor: pointer;
+ transition: background-color 0.2s;
+}
+
+.submitButton:hover {
+ background-color: #3d8df5;
+}
+
+.userInfo h2 {
+ margin-top: 0;
+ margin-bottom: 10px;
+}
+
+
+
diff --git a/src/components/Mitglieder/MitgliederItemEdit/EditPopUp.tsx b/src/components/Mitglieder/MitgliederItemEdit/EditPopUp.tsx
new file mode 100644
index 0000000..2041f95
--- /dev/null
+++ b/src/components/Mitglieder/MitgliederItemEdit/EditPopUp.tsx
@@ -0,0 +1,152 @@
+import { IoCloseOutline } from "react-icons/io5";
+import { useState } from "react";
+import styles from "./EditPopUp.module.css";
+import { User, useOrgUsers } from "../../../hooks/useUsers";
+
+type EditPopUpProps = {
+ closePopup: () => void;
+ refetchUsers: () => Promise;
+ user: User;
+};
+
+const EditPopUp = ({ closePopup, refetchUsers, user }: EditPopUpProps) => {
+ const { updateUser } = useOrgUsers();
+ const [formData, setFormData] = useState({
+ fullName: user.fullName || "",
+ email: user.email || "",
+ privilege: user.privilege || "",
+ username: user.username || ""
+ });
+ const [isSubmitting, setIsSubmitting] = useState(false);
+ const [error, setError] = useState(null);
+
+ const handleChange = (e: React.ChangeEvent) => {
+ const { name, value } = e.target;
+ setFormData(prev => ({
+ ...prev,
+ [name]: value
+ }));
+ };
+
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault();
+ setIsSubmitting(true);
+ setError(null);
+
+ try {
+ await updateUser(user.id, {
+ fullName: formData.fullName,
+ email: formData.email,
+ privilege: formData.privilege,
+ username: formData.username
+ });
+
+ // Refresh the users list and close the popup
+ await refetchUsers();
+ closePopup();
+ } catch (error: any) {
+ setError(error.message || "Failed to update user");
+ } finally {
+ setIsSubmitting(false);
+ }
+ };
+
+ return (
+
+
+
+
+ {error &&
{error}
}
+
+
+
+ );
+};
+
+const popupStyles: { overlay: React.CSSProperties; popup: React.CSSProperties } = {
+ overlay: {
+ position: "fixed",
+ top: 0,
+ left: 0,
+ right: 0,
+ bottom: 0,
+ backgroundColor: "rgba(0,0,0,0.5)",
+ display: "flex",
+ justifyContent: "center",
+ alignItems: "center",
+
+ },
+ popup: {
+ backgroundColor: "#fff",
+ borderRadius: "8px",
+ boxShadow: "0 5px 15px rgba(0,0,0,0.3)",
+ position: "relative",
+ width: "400px",
+ maxWidth: "90%"
+ }
+};
+
+export default EditPopUp;
\ No newline at end of file
diff --git a/src/components/Mitglieder/MitgliederItemEdit/MitgliederItemEdit.module.css b/src/components/Mitglieder/MitgliederItemEdit/MitgliederItemEdit.module.css
new file mode 100644
index 0000000..7c91679
--- /dev/null
+++ b/src/components/Mitglieder/MitgliederItemEdit/MitgliederItemEdit.module.css
@@ -0,0 +1,12 @@
+.editBtn {
+ background: none;
+ border: none;
+ cursor: pointer;
+ font-size: 18px;
+ color: #444;
+ height: 100;
+}
+
+.editBtn:hover {
+ color: #3d8df5;
+}
\ No newline at end of file
diff --git a/src/components/Mitglieder/MitgliederItemEdit/MitgliederItemEdit.tsx b/src/components/Mitglieder/MitgliederItemEdit/MitgliederItemEdit.tsx
new file mode 100644
index 0000000..aa21877
--- /dev/null
+++ b/src/components/Mitglieder/MitgliederItemEdit/MitgliederItemEdit.tsx
@@ -0,0 +1,36 @@
+import { FaEdit } from "react-icons/fa";
+import { useState } from "react";
+import styles from "./MitgliederItemEdit.module.css";
+import EditPopUp from "./EditPopUp";
+import { User } from "../../../hooks/useUsers";
+
+type MitgliederItemEditProps = {
+ user: User;
+ refetchUsers: () => Promise;
+};
+
+const MitgliederItemEdit = ({ user, refetchUsers }: MitgliederItemEditProps) => {
+ const [editWindow, setEditWindow] = useState(false);
+
+ return (
+
+
+
+ {editWindow && (
+ setEditWindow(false)}
+ refetchUsers={refetchUsers}
+ />
+ )}
+
+ );
+};
+
+export default MitgliederItemEdit;
\ No newline at end of file
diff --git a/src/components/Sidebar/Sidebar.tsx b/src/components/Sidebar/Sidebar.tsx
index 93cdd37..dce007f 100644
--- a/src/components/Sidebar/Sidebar.tsx
+++ b/src/components/Sidebar/Sidebar.tsx
@@ -1,9 +1,9 @@
import React from 'react'
import styles from './Sidebar.module.css'
import SidebarItem from './SidebarItem';
-import useSidebarData from './sidebarData';
+import useSidebarData from './SidebarData';
import SidebarUser from './SidebarUser';
-import { useCurrentUser } from '../../hooks/useCurrentUser';
+import { useCurrentUser } from '../../hooks/useUsers';
interface SidebarItemType {
id: string;
diff --git a/src/components/Sidebar/sidebarData.tsx b/src/components/Sidebar/sidebarData.tsx
index d7f3dde..2c399a3 100644
--- a/src/components/Sidebar/sidebarData.tsx
+++ b/src/components/Sidebar/sidebarData.tsx
@@ -50,19 +50,19 @@ export const useSidebarData = () => {
icon: BiInfoSquare,
},
{
- id: '6',
+ id: '7',
name: 'Logs',
link: '',
icon: TbLogs ,
},
{
- id: '7',
+ id: '8',
name: 'Settings',
link: '',
icon: GoGear,
},
{
- id: '8',
+ id: '9',
name: 'Help',
link: '',
icon: BiInfoSquare,
diff --git a/src/hooks/useApi.ts b/src/hooks/useApi.ts
new file mode 100644
index 0000000..d083884
--- /dev/null
+++ b/src/hooks/useApi.ts
@@ -0,0 +1,62 @@
+import { useState } from 'react';
+import api from '../api';
+
+// Generic API error handling
+export function formatApiError(error: any, defaultMessage: string): string {
+ if (error.response) {
+ return error.response.data?.detail || error.response.data?.message || defaultMessage;
+ } else if (error.request) {
+ return 'Keine Antwort vom Server erhalten';
+ } else {
+ return error.message || defaultMessage;
+ }
+}
+
+// Type for API request options
+export interface ApiRequestOptions {
+ url: string;
+ method: 'get' | 'post' | 'put' | 'delete';
+ data?: T;
+ params?: Record;
+ additionalConfig?: Record; // For responseType, headers, etc.
+}
+
+// Hook for making API requests with consistent error handling
+export function useApiRequest() {
+ const [isLoading, setIsLoading] = useState(false);
+ const [error, setError] = useState(null);
+
+ const request = async ({
+ url,
+ method,
+ data,
+ params,
+ additionalConfig = {}
+ }: ApiRequestOptions): Promise => {
+ setIsLoading(true);
+ setError(null);
+
+ try {
+ const response = await api({
+ url,
+ method,
+ data,
+ params,
+ ...additionalConfig
+ });
+ return response.data;
+ } catch (error: any) {
+ const errorMessage = formatApiError(error, `Fehler bei ${method.toUpperCase()} ${url}`);
+ setError(errorMessage);
+ throw new Error(errorMessage);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ return {
+ request,
+ isLoading,
+ error
+ };
+}
\ No newline at end of file
diff --git a/src/hooks/useAuth.ts b/src/hooks/useAuth.ts
deleted file mode 100644
index 35f312c..0000000
--- a/src/hooks/useAuth.ts
+++ /dev/null
@@ -1,92 +0,0 @@
-import { useState } from 'react';
-import axios from 'axios';
-
-interface LoginResponse {
- accessToken: string;
- tokenType: string;
- label?: any;
- fieldLabels?: any;
-}
-
-interface UseAuthReturn {
- login: (username: string, password: string) => Promise;
- error: string | null;
- isLoading: boolean;
-}
-
-export function useAuth(): UseAuthReturn {
- const [error, setError] = useState(null);
- const [isLoading, setIsLoading] = useState(false);
-
- const login = async (username: string, password: string): Promise => {
- setIsLoading(true);
- setError(null);
-
- try {
- // Create the form data in the exact format FastAPI expects
- const params = new URLSearchParams();
- params.append('username', username);
- params.append('password', password);
- params.append('grant_type', 'password');
- params.append('scope', '');
- params.append('client_id', '');
- params.append('client_secret', '');
-
- // Create a custom axios instance for this request
- const instance = axios.create({
- baseURL: 'https://gateway.poweron-center.net',
- withCredentials: true,
- headers: {
- 'Content-Type': 'application/x-www-form-urlencoded'
- }
- });
-
- const response = await instance.post('/api/token', params);
-
- console.log('Login response:', response.data);
-
- // Store the entire auth response
- if (response.data.accessToken) {
- localStorage.setItem('auth_data', JSON.stringify(response.data));
- }
-
- return response.data;
- } catch (error: any) {
- let errorMessage = 'An error occurred during login';
-
- if (error.response) {
- // Log the complete error details including the request that was sent
- console.error('Login error details:', {
- status: error.response.status,
- statusText: error.response.statusText,
- data: error.response.data,
- headers: error.response.headers,
- request: {
- url: error.config?.url,
- method: error.config?.method,
- data: error.config?.data,
- params: error.config?.params
- }
- });
- errorMessage = error.response.data?.detail || 'Invalid username or password';
- } else if (error.request) {
- console.error('No response received:', error.request);
- errorMessage = 'No response received from server';
- } else {
- console.error('Error:', error.message);
- errorMessage = error.message;
- }
-
- setError(errorMessage);
- throw error;
- } finally {
- setIsLoading(false);
- }
- };
-
- return {
- login,
- error,
- isLoading
- };
-}
\ No newline at end of file
diff --git a/src/hooks/useAuthentication.ts b/src/hooks/useAuthentication.ts
new file mode 100644
index 0000000..b0094a6
--- /dev/null
+++ b/src/hooks/useAuthentication.ts
@@ -0,0 +1,290 @@
+import { useState } from 'react';
+import axios from 'axios';
+import { useMsal } from '@azure/msal-react';
+import api from '../api';
+import { useApiRequest } from './useApi';
+
+// Regular authentication
+interface LoginResponse {
+ accessToken: string;
+ tokenType: string;
+ label?: any;
+ fieldLabels?: any;
+}
+
+export function useAuth() {
+ const [error, setError] = useState(null);
+ const [isLoading, setIsLoading] = useState(false);
+
+ const login = async (username: string, password: string): Promise => {
+ setIsLoading(true);
+ setError(null);
+
+ try {
+ // Create the form data in the exact format FastAPI expects
+ const params = new URLSearchParams();
+ params.append('username', username);
+ params.append('password', password);
+ params.append('grant_type', 'password');
+ params.append('scope', '');
+ params.append('client_id', '');
+ params.append('client_secret', '');
+
+ // Create a custom axios instance for this request
+ const instance = axios.create({
+ baseURL: 'https://gateway.poweron-center.net',
+ withCredentials: true,
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded'
+ }
+ });
+
+ const response = await instance.post('/api/token', params);
+
+ // Store the entire auth response
+ if (response.data.accessToken) {
+ localStorage.setItem('auth_data', JSON.stringify(response.data));
+ }
+
+ return response.data;
+ } catch (error: any) {
+ let errorMessage = 'An error occurred during login';
+
+ if (error.response) {
+ errorMessage = error.response.data?.detail || 'Invalid username or password';
+ } else if (error.request) {
+ errorMessage = 'No response received from server';
+ } else {
+ errorMessage = error.message;
+ }
+
+ setError(errorMessage);
+ throw error;
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ return {
+ login,
+ error,
+ isLoading
+ };
+}
+
+// Microsoft Authentication
+interface MsalAuthResponse {
+ accessToken: string;
+ tokenType: string;
+ user: {
+ username: string;
+ email: string;
+ fullName: string;
+ mandateId: number;
+ };
+}
+
+export function useMsalAuth() {
+ const { instance, accounts } = useMsal();
+ const { request, isLoading, error } = useApiRequest();
+ const [msalError, setMsalError] = useState(null);
+ const [isMsalLoading, setIsMsalLoading] = useState(false);
+
+ const loginWithMsal = async (): Promise => {
+ setIsMsalLoading(true);
+ setMsalError(null);
+
+ try {
+ let msalToken;
+
+ // If we have an account, try to get the token silently
+ if (accounts.length > 0) {
+ const silentRequest = {
+ scopes: ['user.read'],
+ account: accounts[0]
+ };
+
+ try {
+ const response = await instance.acquireTokenSilent(silentRequest);
+ msalToken = response.accessToken;
+ } catch (e) {
+ // If silent token acquisition fails, fall back to popup
+ const response = await instance.acquireTokenPopup(silentRequest);
+ msalToken = response.accessToken;
+ }
+ } else {
+ // No account, do popup login
+ const response = await instance.loginPopup({
+ scopes: ['user.read']
+ });
+
+ if (response.account) {
+ const tokenResponse = await instance.acquireTokenSilent({
+ scopes: ['user.read'],
+ account: response.account
+ });
+ msalToken = tokenResponse.accessToken;
+ } else {
+ throw new Error('Failed to get account after login');
+ }
+ }
+
+ // Exchange MSAL token for backend token
+ const response = await api.post('/api/msft/token', null, {
+ headers: {
+ 'Authorization': `Bearer ${msalToken}`
+ }
+ });
+
+ // Store the backend token
+ if (response.data.accessToken) {
+ localStorage.setItem('auth_data', JSON.stringify(response.data));
+ }
+
+ return response.data;
+ } catch (error: any) {
+ let errorMessage = 'MSAL Login fehlgeschlagen';
+
+ if (error.response) {
+ errorMessage = error.response.data?.detail || error.response.data?.message || errorMessage;
+ } else if (error.request) {
+ errorMessage = 'Keine Antwort vom Server erhalten';
+ } else {
+ errorMessage = error.message || errorMessage;
+ }
+
+ setMsalError(errorMessage);
+ throw new Error(errorMessage);
+ } finally {
+ setIsMsalLoading(false);
+ }
+ };
+
+ return {
+ loginWithMsal,
+ error: msalError || error,
+ isLoading: isMsalLoading || isLoading
+ };
+}
+
+// Registration
+interface RegisterData {
+ username: string;
+ password: string;
+ email: string;
+ fullName: string;
+ language?: string;
+}
+
+interface RegisterResponse {
+ success: boolean;
+ message?: string;
+ user?: {
+ username: string;
+ email: string;
+ fullName: string;
+ };
+}
+
+export function useRegister() {
+ const { request, isLoading, error } = useApiRequest();
+
+ const register = async (userData: RegisterData): Promise => {
+ try {
+ // Add default language if not provided
+ const dataToSend = {
+ ...userData,
+ language: userData.language || 'de'
+ };
+
+ const response = await request({
+ url: '/api/users/register',
+ method: 'post',
+ data: dataToSend,
+ additionalConfig: {
+ headers: {
+ 'Content-Type': 'application/json'
+ }
+ }
+ });
+
+ return {
+ success: true,
+ message: 'Registration successful',
+ user: response
+ };
+ } catch (error: any) {
+ throw error;
+ }
+ };
+
+ return {
+ register,
+ error,
+ isLoading
+ };
+}
+
+// Microsoft Registration
+interface MsalRegisterData {
+ username: string;
+ email: string;
+ fullName: string;
+ language?: string;
+}
+
+export function useMsalRegister() {
+ const { instance, accounts } = useMsal();
+ const { request, isLoading, error } = useApiRequest();
+
+ const registerWithMsal = async (): Promise => {
+ try {
+ if (!accounts || accounts.length === 0) {
+ // If not signed in with Microsoft, sign in first
+ await instance.loginPopup({
+ scopes: ['user.read']
+ });
+ }
+
+ // Get the current account
+ const currentAccount = instance.getAllAccounts()[0];
+ if (!currentAccount) {
+ throw new Error('No Microsoft account found');
+ }
+
+ // Prepare user data from Microsoft account
+ const userData: MsalRegisterData = {
+ username: currentAccount.username,
+ email: currentAccount.username,
+ fullName: currentAccount.name || currentAccount.username,
+ language: 'de'
+ };
+
+ // Register the user through our backend
+ const response = await request({
+ url: '/api/users/register-with-msal',
+ method: 'post',
+ data: userData,
+ additionalConfig: {
+ headers: {
+ 'Content-Type': 'application/json'
+ }
+ }
+ });
+
+ return {
+ success: true,
+ message: 'Registration successful',
+ user: response
+ };
+ } catch (error: any) {
+ throw error;
+ }
+ };
+
+ return {
+ registerWithMsal,
+ error,
+ isLoading
+ };
+}
\ No newline at end of file
diff --git a/src/hooks/useCurrentUser.ts b/src/hooks/useCurrentUser.ts
deleted file mode 100644
index 7d8c99d..0000000
--- a/src/hooks/useCurrentUser.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-import { useState, useEffect } from 'react';
-import api from '../api';
-
-interface CurrentUser {
- id: number;
- username: string;
- fullName: string;
- email: string;
- privilege: string;
- mandateId: number;
-}
-
-interface UseCurrentUserReturn {
- user: CurrentUser | null;
- error: string | null;
- isLoading: boolean;
- refetch: () => Promise;
-}
-
-export function useCurrentUser(): UseCurrentUserReturn {
- const [user, setUser] = useState(null);
- const [error, setError] = useState(null);
- const [isLoading, setIsLoading] = useState(false);
-
- const fetchCurrentUser = async () => {
- setIsLoading(true);
- setError(null);
-
- try {
- const response = await api.get('/api/user/me');
- setUser(response.data);
- } catch (error: any) {
- let errorMessage = 'Fehler beim Abrufen des Benutzerprofils';
-
- if (error.response) {
- errorMessage = error.response.data?.detail || error.response.data?.message || errorMessage;
- } else if (error.request) {
- errorMessage = 'Keine Antwort vom Server erhalten';
- } else {
- errorMessage = error.message || errorMessage;
- }
-
- setError(errorMessage);
- setUser(null);
- } finally {
- setIsLoading(false);
- }
- };
-
- useEffect(() => {
- fetchCurrentUser();
- }, []);
-
- return {
- user,
- error,
- isLoading,
- refetch: fetchCurrentUser
- };
-}
\ No newline at end of file
diff --git a/src/hooks/useFiles.ts b/src/hooks/useFiles.ts
new file mode 100644
index 0000000..85d063a
--- /dev/null
+++ b/src/hooks/useFiles.ts
@@ -0,0 +1,209 @@
+import { useState, useEffect } from 'react';
+import { useApiRequest } from './useApi';
+
+// File interfaces
+export interface FileInfo {
+ id: number;
+ name: string;
+ mimeType: string;
+ size?: number;
+ creationDate: string;
+ fileHash?: string;
+ mandateId?: number;
+ userId?: number;
+ workflowId?: string;
+}
+
+export interface UserFile {
+ id: number;
+ file_name: string;
+ action: string;
+ created_at: string;
+ size?: number;
+}
+
+// Files list hook
+export function useUserFiles() {
+ const [files, setFiles] = useState([]);
+ const { request, isLoading: loading, error } = useApiRequest();
+
+ const fetchFiles = async () => {
+ try {
+ const data = await request({
+ url: '/api/files',
+ method: 'get'
+ });
+
+ // Map API response to our frontend model
+ const mappedFiles = data.map((apiFile: FileInfo): UserFile => {
+ // Derive a simplified action from the MIME type
+ let action = 'Document';
+ if (apiFile.mimeType) {
+ const mimePrefix = apiFile.mimeType.split('/')[0];
+ switch (mimePrefix) {
+ case 'image':
+ action = 'Bild';
+ break;
+ case 'application':
+ if (apiFile.mimeType.includes('pdf')) {
+ action = 'PDF';
+ } else if (apiFile.mimeType.includes('word') || apiFile.mimeType.includes('office')) {
+ action = 'Dokument';
+ } else if (apiFile.mimeType.includes('excel') || apiFile.mimeType.includes('spreadsheet')) {
+ action = 'Tabelle';
+ } else {
+ action = 'Datei';
+ }
+ break;
+ case 'text':
+ action = 'Text';
+ break;
+ case 'video':
+ action = 'Video';
+ break;
+ case 'audio':
+ action = 'Audio';
+ break;
+ default:
+ action = 'Datei';
+ }
+ }
+
+ return {
+ id: apiFile.id,
+ file_name: apiFile.name,
+ action: action,
+ created_at: apiFile.creationDate,
+ size: apiFile.size
+ };
+ });
+
+ setFiles(mappedFiles);
+ } catch (error) {
+ // Error is already handled by useApiRequest
+ }
+ };
+
+ useEffect(() => {
+ fetchFiles();
+ }, []);
+
+ return { files, loading, error, refetch: fetchFiles };
+}
+
+// File operations hook
+export function useFileOperations() {
+ const [downloadingFiles, setDownloadingFiles] = useState>(new Set());
+ const [deletingFiles, setDeletingFiles] = useState>(new Set());
+ const [uploadingFile, setUploadingFile] = useState(false);
+ const { request, error: apiError, isLoading } = useApiRequest();
+ const [downloadError, setDownloadError] = useState(null);
+ const [deleteError, setDeleteError] = useState(null);
+ const [uploadError, setUploadError] = useState(null);
+
+ const handleFileDownload = async (fileId: number, fileName: string) => {
+ setDownloadError(null);
+ setDownloadingFiles(prev => new Set(prev).add(fileId));
+
+ try {
+ const blob = await request({
+ url: `/api/files/${fileId}`,
+ method: 'get',
+ // Override axios config for blob response
+ additionalConfig: { responseType: 'blob' }
+ });
+
+ // Create a download link and trigger the download
+ const url = window.URL.createObjectURL(new Blob([blob]));
+ const link = document.createElement('a');
+ link.href = url;
+ link.setAttribute('download', fileName);
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+ window.URL.revokeObjectURL(url);
+
+ return true;
+ } catch (error: any) {
+ setDownloadError(error.message);
+ return false;
+ } finally {
+ setDownloadingFiles(prev => {
+ const newSet = new Set(prev);
+ newSet.delete(fileId);
+ return newSet;
+ });
+ }
+ };
+
+ const handleFileDelete = async (fileId: number) => {
+ setDeleteError(null);
+ setDeletingFiles(prev => new Set(prev).add(fileId));
+
+ try {
+ await request({
+ url: `/api/files/${fileId}`,
+ method: 'delete'
+ });
+
+ // Add a small delay to ensure backend has time to process
+ await new Promise(resolve => setTimeout(resolve, 300));
+ return true;
+ } catch (error: any) {
+ setDeleteError(error.message);
+ return false;
+ } finally {
+ setDeletingFiles(prev => {
+ const newSet = new Set(prev);
+ newSet.delete(fileId);
+ return newSet;
+ });
+ }
+ };
+
+ const handleFileUpload = async (file: globalThis.File, workflowId?: string) => {
+ setUploadError(null);
+ setUploadingFile(true);
+
+ try {
+ const formData = new FormData();
+ formData.append('file', file);
+
+ if (workflowId) {
+ formData.append('workflowId', workflowId);
+ }
+
+ const fileData = await request({
+ url: '/api/files/upload',
+ method: 'post',
+ data: formData,
+ // Override axios config for form data
+ additionalConfig: {
+ headers: {
+ 'Content-Type': 'multipart/form-data',
+ }
+ }
+ });
+
+ return { success: true, fileData };
+ } catch (error: any) {
+ setUploadError(error.message);
+ return { success: false, error: error.message };
+ } finally {
+ setUploadingFile(false);
+ }
+ };
+
+ return {
+ downloadingFiles,
+ deletingFiles,
+ uploadingFile,
+ downloadError,
+ deleteError,
+ uploadError,
+ handleFileDownload,
+ handleFileDelete,
+ handleFileUpload,
+ isLoading
+ };
+}
\ No newline at end of file
diff --git a/src/hooks/useMsalAuth.ts b/src/hooks/useMsalAuth.ts
deleted file mode 100644
index 69dfc5e..0000000
--- a/src/hooks/useMsalAuth.ts
+++ /dev/null
@@ -1,117 +0,0 @@
-import { useState } from 'react';
-import { useMsal } from '@azure/msal-react';
-import api from '../api';
-
-interface MsalAuthResponse {
- accessToken: string;
- tokenType: string;
- user: {
- username: string;
- email: string;
- fullName: string;
- mandateId: number;
- };
-}
-
-interface UseMsalAuthReturn {
- loginWithMsal: () => Promise;
- error: string | null;
- isLoading: boolean;
-}
-
-export function useMsalAuth(): UseMsalAuthReturn {
- const { instance, accounts } = useMsal();
- const [error, setError] = useState(null);
- const [isLoading, setIsLoading] = useState(false);
-
- const loginWithMsal = async (): Promise => {
- setIsLoading(true);
- setError(null);
-
- try {
- let msalToken;
- console.log("Starting MSAL auth process");
-
- // If we have an account, try to get the token silently
- if (accounts.length > 0) {
- console.log("Account found, trying silent token acquisition");
- const silentRequest = {
- scopes: ['user.read'],
- account: accounts[0]
- };
-
- try {
- const response = await instance.acquireTokenSilent(silentRequest);
- msalToken = response.accessToken;
- console.log("Silent token acquisition successful");
- } catch (e) {
- // If silent token acquisition fails, fall back to popup
- console.log("Silent token acquisition failed, trying popup", e);
- const response = await instance.acquireTokenPopup(silentRequest);
- msalToken = response.accessToken;
- console.log("Popup token acquisition successful");
- }
- } else {
- // No account, do popup login
- console.log("No account found, doing popup login");
- const response = await instance.loginPopup({
- scopes: ['user.read']
- });
-
- if (response.account) {
- console.log("Login successful, acquiring token");
- const tokenResponse = await instance.acquireTokenSilent({
- scopes: ['user.read'],
- account: response.account
- });
- msalToken = tokenResponse.accessToken;
- console.log("Token acquisition successful");
- } else {
- throw new Error('Failed to get account after login');
- }
- }
-
- console.log("MSAL token acquired, exchanging with backend");
-
- // Exchange MSAL token for backend token
- const response = await api.post('/api/msft/token', null, {
- headers: {
- 'Authorization': `Bearer ${msalToken}`
- }
- });
-
- console.log("Backend token exchange successful", response.data);
-
- // Store the backend token
- if (response.data.accessToken) {
- localStorage.setItem('auth_data', JSON.stringify(response.data));
- }
-
- return response.data;
- } catch (error: any) {
- let errorMessage = 'MSAL Login fehlgeschlagen';
-
- if (error.response) {
- console.error("Backend response error:", error.response.status, error.response.data);
- errorMessage = error.response.data?.detail || error.response.data?.message || errorMessage;
- } else if (error.request) {
- console.error("No response received:", error.request);
- errorMessage = 'Keine Antwort vom Server erhalten';
- } else {
- console.error("Error during MSAL auth:", error.message);
- errorMessage = error.message || errorMessage;
- }
-
- setError(errorMessage);
- throw new Error(errorMessage);
- } finally {
- setIsLoading(false);
- }
- };
-
- return {
- loginWithMsal,
- error,
- isLoading
- };
-}
\ No newline at end of file
diff --git a/src/hooks/useMsalRegister.ts b/src/hooks/useMsalRegister.ts
deleted file mode 100644
index 6f1a0ce..0000000
--- a/src/hooks/useMsalRegister.ts
+++ /dev/null
@@ -1,94 +0,0 @@
-import { useState } from 'react';
-import { useMsal } from '@azure/msal-react';
-import api from '../api';
-
-interface MsalRegisterData {
- username: string;
- email: string;
- fullName: string;
- language?: string;
-}
-
-interface MsalRegisterResponse {
- success: boolean;
- message?: string;
- user?: {
- username: string;
- email: string;
- fullName: string;
- };
-}
-
-interface UseMsalRegisterReturn {
- registerWithMsal: () => Promise;
- error: string | null;
- isLoading: boolean;
-}
-
-export function useMsalRegister(): UseMsalRegisterReturn {
- const { instance, accounts } = useMsal();
- const [error, setError] = useState(null);
- const [isLoading, setIsLoading] = useState(false);
-
- const registerWithMsal = async (): Promise => {
- setIsLoading(true);
- setError(null);
-
- try {
- if (!accounts || accounts.length === 0) {
- // If not signed in with Microsoft, sign in first
- await instance.loginPopup({
- scopes: ['user.read']
- });
- }
-
- // Get the current account
- const currentAccount = instance.getAllAccounts()[0];
- if (!currentAccount) {
- throw new Error('No Microsoft account found');
- }
-
- // Prepare user data from Microsoft account
- const userData: MsalRegisterData = {
- username: currentAccount.username,
- email: currentAccount.username,
- fullName: currentAccount.name || currentAccount.username,
- language: 'de'
- };
-
- // Register the user through our backend
- const response = await api.post('/api/users/register-with-msal', userData, {
- headers: {
- 'Content-Type': 'application/json'
- }
- });
-
- return {
- success: true,
- message: 'Registration successful',
- user: response.data
- };
- } catch (error: any) {
- let errorMessage = 'MSAL Registrierung fehlgeschlagen';
-
- if (error.response) {
- errorMessage = error.response.data?.detail || error.response.data?.message || errorMessage;
- } else if (error.request) {
- errorMessage = 'Keine Antwort vom Server erhalten';
- } else {
- errorMessage = error.message || errorMessage;
- }
-
- setError(errorMessage);
- throw new Error(errorMessage);
- } finally {
- setIsLoading(false);
- }
- };
-
- return {
- registerWithMsal,
- error,
- isLoading
- };
-}
\ No newline at end of file
diff --git a/src/hooks/useRegister.ts b/src/hooks/useRegister.ts
deleted file mode 100644
index 4417f00..0000000
--- a/src/hooks/useRegister.ts
+++ /dev/null
@@ -1,82 +0,0 @@
-import { useState } from 'react';
-import api from '../api';
-
-interface RegisterData {
- username: string;
- password: string;
- email: string;
- fullName: string;
- language?: string;
-}
-
-interface RegisterResponse {
- success: boolean;
- message?: string;
- user?: {
- username: string;
- email: string;
- fullName: string;
- };
-}
-
-interface UseRegisterReturn {
- register: (data: RegisterData) => Promise;
- error: string | null;
- isLoading: boolean;
-}
-
-export function useRegister(): UseRegisterReturn {
- const [error, setError] = useState(null);
- const [isLoading, setIsLoading] = useState(false);
-
- const register = async (userData: RegisterData): Promise => {
- setIsLoading(true);
- setError(null);
-
- try {
- // Add default language if not provided
- const dataToSend = {
- ...userData,
- language: userData.language || 'de'
- };
-
- const response = await api.post('/api/users/register', dataToSend, {
- headers: {
- 'Content-Type': 'application/json'
- }
- });
-
- return {
- success: true,
- message: 'Registration successful',
- user: response.data
- };
- } catch (error: any) {
- let errorMessage = 'Registrierung fehlgeschlagen';
-
- // Handle different types of errors
- if (error.response) {
- // The request was made and the server responded with a status code
- // that falls out of the range of 2xx
- errorMessage = error.response.data?.detail || error.response.data?.message || errorMessage;
- } else if (error.request) {
- // The request was made but no response was received
- errorMessage = 'Keine Antwort vom Server erhalten';
- } else {
- // Something happened in setting up the request
- errorMessage = error.message || errorMessage;
- }
-
- setError(errorMessage);
- throw new Error(errorMessage);
- } finally {
- setIsLoading(false);
- }
- };
-
- return {
- register,
- error,
- isLoading
- };
-}
\ No newline at end of file
diff --git a/src/hooks/useUser.ts b/src/hooks/useUser.ts
deleted file mode 100644
index aefd67d..0000000
--- a/src/hooks/useUser.ts
+++ /dev/null
@@ -1,105 +0,0 @@
-import { useState } from 'react';
-import api from '../api';
-
-interface User {
- id: number;
- username: string;
- email: string;
- fullName: string;
- mandateId: number;
-}
-
-interface UseUserReturn {
- deleteUser: (userId: number) => Promise;
- updateUser: (userId: number, userData: Partial) => Promise;
- getUser: (userId: number) => Promise;
- error: string | null;
- isLoading: boolean;
-}
-
-export function useUser(): UseUserReturn {
- const [error, setError] = useState(null);
- const [isLoading, setIsLoading] = useState(false);
-
- const deleteUser = async (userId: number): Promise => {
- setIsLoading(true);
- setError(null);
-
- try {
- await api.delete(`/api/users/${userId}`);
- } catch (error: any) {
- let errorMessage = 'Fehler beim Löschen des Benutzers';
-
- if (error.response) {
- errorMessage = error.response.data?.detail || error.response.data?.message || errorMessage;
- } else if (error.request) {
- errorMessage = 'Keine Antwort vom Server erhalten';
- } else {
- errorMessage = error.message || errorMessage;
- }
-
- setError(errorMessage);
- throw new Error(errorMessage);
- } finally {
- setIsLoading(false);
- }
- };
-
- const updateUser = async (userId: number, userData: Partial): Promise => {
- setIsLoading(true);
- setError(null);
-
- try {
- const response = await api.put(`/api/users/${userId}`, userData);
- return response.data;
- } catch (error: any) {
- let errorMessage = 'Fehler beim Aktualisieren des Benutzers';
-
- if (error.response) {
- errorMessage = error.response.data?.detail || error.response.data?.message || errorMessage;
- } else if (error.request) {
- errorMessage = 'Keine Antwort vom Server erhalten';
- } else {
- errorMessage = error.message || errorMessage;
- }
-
- setError(errorMessage);
- throw new Error(errorMessage);
- } finally {
- setIsLoading(false);
- }
- };
-
- const getUser = async (userId: number): Promise => {
- setIsLoading(true);
- setError(null);
-
- try {
- const response = await api.get(`/api/users/${userId}`);
- return response.data;
- } catch (error: any) {
- let errorMessage = 'Fehler beim Abrufen des Benutzers';
-
- if (error.response) {
- errorMessage = error.response.data?.detail || error.response.data?.message || errorMessage;
- } else if (error.request) {
- errorMessage = 'Keine Antwort vom Server erhalten';
- } else {
- errorMessage = error.message || errorMessage;
- }
-
- setError(errorMessage);
- throw new Error(errorMessage);
- } finally {
- setIsLoading(false);
- }
- };
-
- return {
- deleteUser,
- updateUser,
- getUser,
- error,
- isLoading
- };
-}
\ No newline at end of file
diff --git a/src/hooks/useUsers.ts b/src/hooks/useUsers.ts
new file mode 100644
index 0000000..70ae9fa
--- /dev/null
+++ b/src/hooks/useUsers.ts
@@ -0,0 +1,134 @@
+import { useState, useEffect } from 'react';
+import { useApiRequest } from './useApi';
+
+// User interfaces
+export interface User {
+ id: number;
+ username: string;
+ email: string;
+ fullName: string;
+ privilege: string;
+ mandateId: number;
+}
+
+export type UserUpdateData = Partial>;
+
+// Current user hook
+export function useCurrentUser() {
+ const [user, setUser] = useState(null);
+ const { request, isLoading, error } = useApiRequest();
+
+ const fetchCurrentUser = async () => {
+ try {
+ const data = await request({
+ url: '/api/user/me',
+ method: 'get'
+ });
+ setUser(data);
+ } catch (error) {
+ setUser(null);
+ }
+ };
+
+ useEffect(() => {
+ fetchCurrentUser();
+ }, []);
+
+ return {
+ user,
+ error,
+ isLoading,
+ refetch: fetchCurrentUser
+ };
+}
+
+// Organization users hook (list, update, delete)
+export function useOrgUsers() {
+ const [users, setUsers] = useState([]);
+ const { request, isLoading: loading, error } = useApiRequest();
+
+ const fetchUsers = async () => {
+ try {
+ const data = await request({
+ url: '/api/users',
+ method: 'get'
+ });
+ setUsers(data);
+ } catch (error) {
+ // Error is already handled by useApiRequest
+ }
+ };
+
+ const updateUser = async (userId: number, userData: UserUpdateData) => {
+ await request({
+ url: `/api/users/${userId}`,
+ method: 'put',
+ data: userData
+ });
+ await fetchUsers(); // Refresh the list after update
+ };
+
+ const deleteUser = async (userId: number) => {
+ await request({
+ url: `/api/users/${userId}`,
+ method: 'delete'
+ });
+ await fetchUsers(); // Refresh the list after deletion
+ };
+
+ const getUser = async (userId: number): Promise => {
+ return await request({
+ url: `/api/users/${userId}`,
+ method: 'get'
+ });
+ };
+
+ useEffect(() => {
+ fetchUsers();
+ }, []);
+
+ return {
+ users,
+ loading,
+ error,
+ refetch: fetchUsers,
+ updateUser,
+ deleteUser,
+ getUser
+ };
+}
+
+// Individual user operations hook (for use when you don't need the full list)
+export function useUser() {
+ const { request, isLoading, error } = useApiRequest();
+
+ const getUser = async (userId: number): Promise => {
+ return await request({
+ url: `/api/users/${userId}`,
+ method: 'get'
+ });
+ };
+
+ const updateUser = async (userId: number, userData: UserUpdateData): Promise => {
+ return await request({
+ url: `/api/users/${userId}`,
+ method: 'put',
+ data: userData
+ });
+ };
+
+ const deleteUser = async (userId: number): Promise => {
+ await request({
+ url: `/api/users/${userId}`,
+ method: 'delete'
+ });
+ };
+
+ return {
+ getUser,
+ updateUser,
+ deleteUser,
+ isLoading,
+ error
+ };
+}
\ No newline at end of file
diff --git a/src/main.tsx b/src/main.tsx
index 19602fe..2981bd1 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -1,7 +1,7 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import App from './App.tsx'
-import { AuthProvider } from './auth/auth-provider.tsx';
+import { AuthProvider } from './auth/authProvider.tsx';
createRoot(document.getElementById('root')!).render(
diff --git a/src/pages/Dateien/Dateien.module.css b/src/pages/Dateien/Dateien.module.css
new file mode 100644
index 0000000..b08da96
--- /dev/null
+++ b/src/pages/Dateien/Dateien.module.css
@@ -0,0 +1,126 @@
+.dateienContainer {
+ margin: 51px 49px 0 36px;
+ display: flex;
+ padding: 0px 30px 30px 30px;
+ flex-direction: column;
+ align-self: stretch;
+ border-radius: 30px;
+ border: 1px solid var(--f-1-f-1-f-1, #F1F1F1);
+ background: var(--Grayscale-True-White, #FFF);
+ position: relative;
+ box-shadow: 0px 2px 6px 0px rgba(194, 194, 194, 0.10);
+ max-height: calc(100vh - 100px);
+ overflow: hidden;
+}
+
+.horizontalLineLight {
+ width: 100%;
+ background-color: #F1F1F1;
+ height: 1px;
+ margin-top: 90px;
+ margin-left: -30px;
+ position: absolute;
+}
+
+.header {
+ display: flex;
+ gap: 30px;
+ align-items: flex-start;
+ height: 62px;
+ color: var(--Grayscale-Black, #24262B);
+ padding-top: 30px;
+}
+
+.datei_hinzufügen_button {
+ border-radius: 30px;
+ background: var(--Grayscale-Gray, #E9E9E9);
+ border: none;
+ outline: none;
+ text-align: left;
+ padding-left: 20px;
+ padding-right: 20px;
+ padding-top: 10px;
+ padding-bottom: 10px;
+ display: flex;
+ gap: 10px;
+ align-items: center;
+}
+
+.datei_hinzufügen_button:hover {
+ cursor: pointer;
+}
+
+.add_icon {
+ font-size: 16px;
+}
+
+/* Files table container */
+.filesTable {
+ width: 100%;
+ margin-top: 20px;
+ display: flex;
+ flex-direction: column;
+ overflow-y: auto;
+}
+
+/* Table header with exact grid positioning to match the screenshot */
+.tableHeader {
+ display: grid;
+ grid-template-columns: 45% 15% 15% 25%;
+ align-items: center;
+ height: 40px;
+ padding: 0px 16px;
+ position: sticky;
+ top: 0;
+ z-index: 10;
+}
+
+/* Header cells with exact positioning */
+.headerCell {
+ display: flex;
+ align-items: center;
+ font-weight: 500;
+ font-size: 14px;
+ color: #333;
+ cursor: pointer;
+ white-space: nowrap;
+ padding-left: 0;
+}
+
+/* Adjust first column for icon space */
+.headerCell:nth-child(1) {
+ padding-left: 30px;
+}
+
+/* Simple sort icon styling */
+.sortIcon {
+ margin-left: 6px;
+ font-size: 14px;
+}
+
+/* Modify the file list to use the same grid layout */
+.filesList {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+ width: 100%;
+}
+
+/* Override the flex layout in DateienItem to force matching the header */
+.filesList li {
+ display: grid !important;
+ grid-template-columns: 45% 15% 15% 25%;
+ border-bottom: 1px solid #f1f1f1;
+ height: 60px;
+ padding: 0 16px;
+ align-items: center;
+}
+
+.error {
+ color: #d32f2f;
+ margin: 1rem 0;
+ padding: 0.5rem;
+ background-color: #ffebee;
+ border-radius: 4px;
+ text-align: center;
+}
\ No newline at end of file
diff --git a/src/pages/Dateien/Dateien.tsx b/src/pages/Dateien/Dateien.tsx
new file mode 100644
index 0000000..5005f68
--- /dev/null
+++ b/src/pages/Dateien/Dateien.tsx
@@ -0,0 +1,166 @@
+import styles from './Dateien.module.css'
+import { IoAddCircleOutline } from "react-icons/io5";
+import { FaSort, FaSortUp, FaSortDown } from "react-icons/fa";
+import DateienItem from '../../components/Dateien/DateienItem';
+import DateienUpload from './DateienHinzufügen/DateienUpload';
+import { useState } from 'react';
+import { useUserFiles, useFileOperations } from '../../hooks/useFiles';
+
+// Define the file type interface
+interface UserFile {
+ id: number;
+ file_name: string;
+ action: string;
+ created_at: string;
+ size?: number;
+}
+
+// Sort types
+type SortField = 'file_name' | 'action' | 'size' | 'created_at';
+type SortDirection = 'asc' | 'desc';
+
+function Dateien() {
+ const { files, loading, error, refetch } = useUserFiles();
+ const [isUploadOpen, setIsUploadOpen] = useState(false);
+ const { uploadError, downloadError, deleteError } = useFileOperations();
+ const [sortField, setSortField] = useState('created_at');
+ const [sortDirection, setSortDirection] = useState('desc');
+
+ // Single function to handle file refresh
+ const refreshFiles = () => {
+ console.log('Refreshing files list');
+ refetch();
+ };
+
+ const handleFileUpload = async (file: File) => {
+ console.log('File upload completed:', file.name);
+ };
+
+ const handleUploadClose = () => {
+ setIsUploadOpen(false);
+ // Refresh files when upload modal is closed
+ setTimeout(() => {
+ refreshFiles();
+ }, 300);
+ };
+
+ const handleFileDeleted = () => {
+ refreshFiles();
+ };
+
+ // Handle sorting
+ const handleSort = (field: SortField) => {
+ if (field === sortField) {
+ // Toggle direction if same field is clicked
+ setSortDirection(sortDirection === 'asc' ? 'desc' : 'asc');
+ } else {
+ // Set new field and default to ascending for most fields, descending for dates
+ setSortField(field);
+ setSortDirection(field === 'created_at' ? 'desc' : 'asc');
+ }
+ };
+
+ // Sort files
+ const sortedFiles = [...files].sort((a, b) => {
+ let result = 0;
+
+ switch (sortField) {
+ case 'file_name':
+ result = a.file_name.localeCompare(b.file_name);
+ break;
+ case 'action':
+ result = a.action.localeCompare(b.action);
+ break;
+ case 'size':
+ // Handle undefined sizes gracefully
+ const sizeA = a.size ?? 0;
+ const sizeB = b.size ?? 0;
+ result = sizeA - sizeB;
+ break;
+ case 'created_at':
+ result = new Date(a.created_at).getTime() - new Date(b.created_at).getTime();
+ break;
+ }
+
+ // Apply sort direction
+ return sortDirection === 'asc' ? result : -result;
+ });
+
+ // Helper to render sort icon
+ const renderSortIcon = (field: SortField) => {
+ if (sortField !== field) return ;
+ return sortDirection === 'asc' ?
+ :
+ ;
+ };
+
+ return (
+
+
+
+
+
+
+
+
+ {(uploadError || downloadError || deleteError) && (
+
+ {uploadError || downloadError || deleteError}
+
+ )}
+ {loading &&
Loading files...
}
+ {error &&
Error: {error}
}
+
+ {!loading && !error && files.length === 0 ? (
+
No files found.
+ ) : (
+
+ {/* Table Headers */}
+
+
handleSort('file_name')}>
+ Name
+ {renderSortIcon('file_name')}
+
+
handleSort('action')}>
+ Typ
+ {renderSortIcon('action')}
+
+
handleSort('size')}>
+ Größe
+ {renderSortIcon('size')}
+
+
handleSort('created_at')}>
+ Datum
+ {renderSortIcon('created_at')}
+
+
+
+
+ {/* Files List */}
+
+ {sortedFiles.map((file: UserFile) => (
+
+ ))}
+
+
+ )}
+
+ );
+}
+
+export default Dateien;
+
diff --git a/src/pages/Dateien/DateienHinzufügen/DateienUpload.module.css b/src/pages/Dateien/DateienHinzufügen/DateienUpload.module.css
new file mode 100644
index 0000000..b160943
--- /dev/null
+++ b/src/pages/Dateien/DateienHinzufügen/DateienUpload.module.css
@@ -0,0 +1,142 @@
+.overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: rgba(0, 0, 0, 0.5);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ z-index: 1000;
+}
+
+.modal {
+ background: white;
+ padding: 2rem;
+ border-radius: 8px;
+ width: 90%;
+ max-width: 500px;
+ position: relative;
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
+}
+
+.closeButton {
+ position: absolute;
+ top: 1rem;
+ right: 1rem;
+ background: none;
+ border: none;
+ font-size: 1.5rem;
+ cursor: pointer;
+ color: #666;
+}
+
+.closeButton:hover {
+ color: #333;
+}
+
+.closeButton:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+}
+
+.uploadStatus {
+ padding: 1rem;
+ border-radius: 4px;
+ margin-bottom: 1rem;
+ text-align: center;
+ font-weight: 500;
+}
+
+.uploadStatus.success {
+ background-color: rgba(76, 175, 80, 0.1);
+ color: #388e3c;
+ border: 1px solid #4caf50;
+}
+
+.uploadStatus.error {
+ background-color: rgba(244, 67, 54, 0.1);
+ color: #d32f2f;
+ border: 1px solid #f44336;
+}
+
+.dropzone {
+ border: 2px dashed #ccc;
+ border-radius: 4px;
+ padding: 2rem;
+ text-align: center;
+ cursor: pointer;
+ margin: 1rem 0;
+ transition: all 0.3s ease;
+}
+
+.dropzone.active {
+ border-color: #2196f3;
+ background-color: rgba(33, 150, 243, 0.1);
+}
+
+.dropzone.uploading {
+ border-color: #4caf50;
+ background-color: rgba(76, 175, 80, 0.1);
+ cursor: wait;
+}
+
+.uploadIcon {
+ font-size: 3rem;
+ color: #666;
+ margin-bottom: 1rem;
+}
+
+.dropzoneText {
+ color: #666;
+}
+
+.dropzoneText p {
+ margin: 0.5rem 0;
+}
+
+.browseButton {
+ background-color: #2196f3;
+ color: white;
+ border: none;
+ padding: 0.5rem 1rem;
+ border-radius: 4px;
+ cursor: pointer;
+ margin-top: 0.5rem;
+}
+
+.browseButton:hover {
+ background-color: #1976d2;
+}
+
+.browseButton:disabled {
+ background-color: #90caf9;
+ cursor: not-allowed;
+}
+
+.selectedFile {
+ margin-top: 1rem;
+ padding: 1rem;
+ background-color: #f5f5f5;
+ border-radius: 4px;
+}
+
+.uploadButton {
+ background-color: #4caf50;
+ color: white;
+ border: none;
+ padding: 0.5rem 1rem;
+ border-radius: 4px;
+ cursor: pointer;
+ margin-top: 0.5rem;
+}
+
+.uploadButton:hover {
+ background-color: #388e3c;
+}
+
+.uploadButton:disabled {
+ background-color: #a5d6a7;
+ cursor: not-allowed;
+}
\ No newline at end of file
diff --git a/src/pages/Dateien/DateienHinzufügen/DateienUpload.tsx b/src/pages/Dateien/DateienHinzufügen/DateienUpload.tsx
new file mode 100644
index 0000000..32a6adc
--- /dev/null
+++ b/src/pages/Dateien/DateienHinzufügen/DateienUpload.tsx
@@ -0,0 +1,123 @@
+import React, { useCallback, useState } from 'react';
+import { useDropzone } from 'react-dropzone';
+import styles from './DateienUpload.module.css';
+import { IoCloudUploadOutline } from "react-icons/io5";
+import { IoClose } from "react-icons/io5";
+import { useFileOperations } from '../../../hooks/useFiles';
+
+interface DateienUploadProps {
+ isOpen: boolean;
+ onClose: () => void;
+ onFileUpload: (file: File) => void;
+}
+
+function DateienUpload({ isOpen, onClose, onFileUpload }: DateienUploadProps) {
+ const [selectedFile, setSelectedFile] = useState(null);
+ const [isUploading, setIsUploading] = useState(false);
+ const [uploadStatus, setUploadStatus] = useState<{ success: boolean; message: string } | null>(null);
+ const { handleFileUpload, uploadError } = useFileOperations();
+
+ const onDrop = useCallback((acceptedFiles: File[]) => {
+ if (acceptedFiles.length > 0) {
+ setSelectedFile(acceptedFiles[0]);
+ // Clear previous upload status when selecting a new file
+ setUploadStatus(null);
+ }
+ }, []);
+
+ const { getRootProps, getInputProps, isDragActive } = useDropzone({
+ onDrop,
+ multiple: false
+ });
+
+ const handleUpload = async () => {
+ if (selectedFile) {
+ setIsUploading(true);
+ setUploadStatus(null);
+
+ try {
+ const result = await handleFileUpload(selectedFile);
+
+ if (result.success) {
+ setUploadStatus({
+ success: true,
+ message: 'Datei erfolgreich hochgeladen!'
+ });
+ onFileUpload(selectedFile);
+ setSelectedFile(null);
+ // Close modal after brief success message
+ setTimeout(() => {
+ onClose();
+ }, 1500);
+ } else {
+ setUploadStatus({
+ success: false,
+ message: uploadError || 'Beim Hochladen ist ein Fehler aufgetreten.'
+ });
+ }
+ } catch (error) {
+ setUploadStatus({
+ success: false,
+ message: 'Beim Hochladen ist ein unerwarteter Fehler aufgetreten.'
+ });
+ } finally {
+ setIsUploading(false);
+ }
+ }
+ };
+
+ if (!isOpen) return null;
+
+ return (
+
+
+
+
Datei hochladen
+
+ {uploadStatus && (
+
+ {uploadStatus.message}
+
+ )}
+
+
+
+
+ {isDragActive ? (
+
Datei hier ablegen...
+ ) : isUploading ? (
+
Lädt hoch...
+ ) : (
+
+
Dateien hierher ziehen
+
oder
+
+
+ )}
+
+
+ {selectedFile && !isUploading && !uploadStatus?.success && (
+
+
Ausgewählte Datei: {selectedFile.name}
+
+
+ )}
+
+
+ );
+}
+
+export default DateienUpload;
\ No newline at end of file
diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx
index fb7694b..a6c1ed1 100644
--- a/src/pages/Login.tsx
+++ b/src/pages/Login.tsx
@@ -1,12 +1,11 @@
import { useMsal } from '@azure/msal-react';
-import { loginRequest } from '../auth/auth-config';
+import { loginRequest } from '../auth/authConfig';
import { useNavigate, useLocation } from 'react-router-dom';
import { useState, useEffect } from 'react';
import styles from './Login.module.css';
import agentDiagram from '../assets/Frame 43.png';
import logo from '../assets/LogoPowerOn.png';
-import { useAuth } from '../hooks/useAuth';
-import { useMsalAuth } from '../hooks/useMsalAuth';
+import { useAuth, useMsalAuth } from '../hooks/useAuthentication';
function Login() {
const { instance, accounts, inProgress } = useMsal();
diff --git a/src/pages/Mitglieder/Mitglieder.module.css b/src/pages/Mitglieder/Mitglieder.module.css
new file mode 100644
index 0000000..b22b137
--- /dev/null
+++ b/src/pages/Mitglieder/Mitglieder.module.css
@@ -0,0 +1,85 @@
+.mitgliederContainer {
+ margin: 51px 49px 0 36px;
+ display: flex;
+ padding: 0px 30px 30px 30px;
+ flex-direction: column;
+ align-self: stretch;
+ border-radius: 30px;
+ border: 1px solid var(--f-1-f-1-f-1, #F1F1F1);
+ background: var(--Grayscale-True-White, #FFF);
+ position: relative;
+ box-shadow: 0px 2px 6px 0px rgba(194, 194, 194, 0.10);
+ max-height: calc(100vh - 100px);
+ overflow: hidden;
+}
+
+.horizontalLineLight {
+ width: 100%;
+ background-color: #F1F1F1;
+ height: 1px;
+ margin-top: 90px;
+ margin-left: -30px;
+ position: absolute;
+}
+
+.header{
+ display: flex;
+ gap: 30px;
+ align-items: flex-start;
+ height: 62px;
+ color: var(--Grayscale-Black, #24262B);
+ padding-top: 30px;
+ padding-bottom: 30px;
+}
+
+.mitglieder_hinzufügen_button {
+
+ border-radius: 30px;
+ background: var(--Grayscale-Gray, #E9E9E9);
+
+ border: none;
+ outline: none;
+ text-align: left;
+ padding-left: 20px;
+ padding-right: 20px;
+ padding-top: 10px;
+ padding-bottom: 10px;
+
+ display: flex;
+ gap: 10px;
+ align-items: center;
+}
+
+.mitglieder_hinzufügen_button:hover {
+ cursor: pointer;
+}
+
+.add_icon {
+ font-size: 16px;
+}
+
+.membersList {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+ width: 100%;
+ overflow-y: auto; /* Enable vertical scrolling */
+ /* Space for the header line */
+}
+
+.membersList li {
+ display: flex;
+ align-items: center;
+ height: 60px; /* Specific height for each item */
+ padding: 0 16px;
+ border-bottom: 1px solid #F1F1F1;
+ font-size: 16px;
+ transition: background-color 0.2s ease;
+ }
+
+.actions {
+ display: flex;
+ gap: 10px;
+ align-items: center;
+ justify-content: center;
+ }
\ No newline at end of file
diff --git a/src/pages/Mitglieder/Mitglieder.tsx b/src/pages/Mitglieder/Mitglieder.tsx
new file mode 100644
index 0000000..839e5e5
--- /dev/null
+++ b/src/pages/Mitglieder/Mitglieder.tsx
@@ -0,0 +1,43 @@
+import styles from './Mitglieder.module.css'
+
+import MitgliederItem from '../../components/Mitglieder/MitgliederItem';
+import { IoPersonAddSharp } from "react-icons/io5";
+import { useOrgUsers } from '../../hooks/useUsers';
+
+function Mitglieder () {
+ const { users, loading, error, refetch } = useOrgUsers();
+
+ return (
+
+
+
+
+
+
+ {loading ? (
+
Loading...
+ ) : error ? (
+
Error: {error}
+ ) : users.length === 0 ? (
+
No users found.
+ ) : (
+
+ {users.map((user) => (
+
+ ))}
+
+ )}
+
+ );
+}
+
+export default Mitglieder;
+
diff --git a/src/pages/Register.tsx b/src/pages/Register.tsx
index bebfa6d..9de5715 100644
--- a/src/pages/Register.tsx
+++ b/src/pages/Register.tsx
@@ -3,9 +3,7 @@ import { useNavigate } from 'react-router-dom';
import styles from './Register.module.css';
import logo from '../assets/LogoPowerOn.png';
import agentDiagram from '../assets/Frame 43.png';
-import { useRegister } from '../hooks/useRegister';
-import { useMsalRegister } from '../hooks/useMsalRegister';
-
+import { useRegister, useMsalRegister } from '../hooks/useAuthentication';
interface RegisterFormData {
username: string;