fixed login

This commit is contained in:
2025-10-09 16:37:43 +02:00
parent adc47c2480
commit 4dcff0f70e
10 changed files with 354 additions and 234 deletions

View File

@@ -14,7 +14,7 @@ import Settings from './pages/Settings/Settings';
import Help from './pages/Help/Help';
import Setup from './pages/Setup/Setup';
// Protected Route Component direkt in App.tsx
// Protected Route Component
const ProtectedRoute: React.FC<{ children: React.ReactNode; roles?: string[] }> = ({
children,
roles = ['admin', 'instandhalter', 'user']
@@ -47,6 +47,89 @@ const ProtectedRoute: React.FC<{ children: React.ReactNode; roles?: string[] }>
return <Layout>{children}</Layout>;
};
// SetupWrapper Component
const SetupWrapper: React.FC = () => {
return (
<Router>
<Setup />
</Router>
);
};
// LoginWrapper Component
const LoginWrapper: React.FC = () => {
return (
<Router>
<Login />
</Router>
);
};
// Main App Content
const AppContent: React.FC = () => {
const { loading, needsSetup, user } = useAuth();
if (loading) {
return (
<div style={{ textAlign: 'center', padding: '40px' }}>
<div> Lade Anwendung...</div>
</div>
);
}
console.log('AppContent - needsSetup:', needsSetup, 'user:', user);
// Wenn Setup benötigt wird → Setup zeigen (mit Router)
if (needsSetup) {
return <SetupWrapper />;
}
// Wenn kein User eingeloggt ist → Login zeigen (mit Router)
if (!user) {
return <LoginWrapper />;
}
// Wenn User eingeloggt ist → Geschützte Routen zeigen
return (
<Router>
<NotificationContainer />
<Routes>
<Route path="/" element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
} />
<Route path="/shift-plans" element={
<ProtectedRoute>
<ShiftPlanList />
</ProtectedRoute>
} />
<Route path="/shift-plans/new" element={
<ProtectedRoute roles={['admin', 'instandhalter']}>
<ShiftPlanCreate />
</ProtectedRoute>
} />
<Route path="/employees" element={
<ProtectedRoute roles={['admin', 'instandhalter']}>
<EmployeeManagement />
</ProtectedRoute>
} />
<Route path="/settings" element={
<ProtectedRoute roles={['admin']}>
<Settings />
</ProtectedRoute>
} />
<Route path="/help" element={
<ProtectedRoute>
<Help />
</ProtectedRoute>
} />
<Route path="/login" element={<Login />} />
</Routes>
</Router>
);
};
function App() {
return (
<NotificationProvider>
@@ -57,61 +140,4 @@ function App() {
);
}
function AppContent() {
const { loading, needsSetup } = useAuth();
if (loading) {
return (
<div style={{ textAlign: 'center', padding: '40px' }}>
<div> Lade Anwendung...</div>
</div>
);
}
return (
<Router>
<NotificationContainer />
<Routes>
{needsSetup ? (
<Route path="*" element={<Setup />} />
) : (
<>
<Route path="/login" element={<Login />} />
<Route path="/" element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
} />
<Route path="/shift-plans" element={
<ProtectedRoute>
<ShiftPlanList />
</ProtectedRoute>
} />
<Route path="/shift-plans/new" element={
<ProtectedRoute roles={['admin', 'instandhalter']}>
<ShiftPlanCreate />
</ProtectedRoute>
} />
<Route path="/employees" element={
<ProtectedRoute roles={['admin', 'instandhalter']}>
<EmployeeManagement />
</ProtectedRoute>
} />
<Route path="/settings" element={
<ProtectedRoute roles={['admin']}>
<Settings />
</ProtectedRoute>
} />
<Route path="/help" element={
<ProtectedRoute>
<Help />
</ProtectedRoute>
} />
</>
)}
</Routes>
</Router>
);
}
export default App;

View File

@@ -1,9 +1,14 @@
// frontend/src/contexts/AuthContext.tsx
import React, { createContext, useContext, useState, useEffect } from 'react';
import { authService, User, LoginRequest } from '../services/authService';
import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react';
import { Employee } from '../types/employee';
interface LoginRequest {
email: string;
password: string;
}
interface AuthContextType {
user: User | null;
user: Employee | null;
login: (credentials: LoginRequest) => Promise<void>;
logout: () => void;
hasRole: (roles: string[]) => boolean;
@@ -15,102 +20,130 @@ interface AuthContextType {
const AuthContext = createContext<AuthContextType | undefined>(undefined);
export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [user, setUser] = useState<User | null>(null);
interface AuthProviderProps {
children: ReactNode;
}
export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
const [user, setUser] = useState<Employee | null>(null);
const [loading, setLoading] = useState(true);
const [needsSetup, setNeedsSetup] = useState(false);
const [refreshTrigger, setRefreshTrigger] = useState(0);
const checkSetupStatus = async () => {
// Token aus localStorage laden
const getStoredToken = (): string | null => {
return localStorage.getItem('token');
};
// Token in localStorage speichern
const setStoredToken = (token: string) => {
localStorage.setItem('token', token);
};
// Token aus localStorage entfernen
const removeStoredToken = () => {
localStorage.removeItem('token');
};
const checkSetupStatus = async (): Promise<void> => {
try {
const response = await fetch('/api/setup/status');
const response = await fetch('http://localhost:3002/api/setup/status');
if (!response.ok) {
throw new Error('Failed to check setup status');
throw new Error('Setup status check failed');
}
const data = await response.json();
setNeedsSetup(data.needsSetup);
console.log('Setup status response:', data);
setNeedsSetup(data.needsSetup === true);
} catch (error) {
console.error('Error checking setup status:', error);
// If we can't reach the server, assume setup is needed
setNeedsSetup(true);
setNeedsSetup(false);
}
};
// Check setup status and load user on mount
useEffect(() => {
const initializeApp = async () => {
await checkSetupStatus();
// Only try to load user if setup is not needed
if (!needsSetup) {
const savedUser = authService.getCurrentUser();
if (savedUser) {
setUser(savedUser);
console.log('✅ User from localStorage:', savedUser.email);
}
}
setLoading(false);
};
initializeApp();
}, []);
// Update needsSetup when it changes
useEffect(() => {
if (!needsSetup && !user) {
// If setup is complete but no user is loaded, try to load from localStorage
const savedUser = authService.getCurrentUser();
if (savedUser) {
setUser(savedUser);
}
}
}, [needsSetup, user]);
// User vom Server laden wenn nötig
useEffect(() => {
if (refreshTrigger > 0 && !needsSetup) {
const loadUserFromServer = async () => {
const serverUser = await authService.fetchCurrentUser();
if (serverUser) {
setUser(serverUser);
console.log('✅ User from server:', serverUser.email);
}
};
loadUserFromServer();
}
}, [refreshTrigger, needsSetup]);
const login = async (credentials: LoginRequest) => {
const refreshUser = async () => {
try {
console.log('🔄 Attempting login...');
const response = await authService.login(credentials);
setUser(response.user);
console.log('✅ Login successful, user set:', response.user.email);
// Force refresh der App
setRefreshTrigger(prev => prev + 1);
const token = getStoredToken();
console.log('🔄 Refreshing user, token exists:', !!token);
if (!token) {
setLoading(false);
return;
}
const response = await fetch('http://localhost:3002/api/auth/me', {
headers: {
'Authorization': `Bearer ${token}`
}
});
if (response.ok) {
const data = await response.json();
console.log('✅ User refreshed:', data.user);
setUser(data.user);
} else {
console.log('❌ Token invalid, removing from storage');
removeStoredToken();
setUser(null);
}
} catch (error) {
console.error('❌ Login failed:', error);
console.error('Error refreshing user:', error);
removeStoredToken();
setUser(null);
} finally {
setLoading(false);
}
};
const login = async (credentials: LoginRequest): Promise<void> => {
try {
console.log('🔐 Attempting login for:', credentials.email);
const response = await fetch('http://localhost:3002/api/auth/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(credentials),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.error || 'Login failed');
}
const data = await response.json();
console.log('✅ Login successful, storing token');
// Token persistent speichern
setStoredToken(data.token);
setUser(data.user);
} catch (error) {
console.error('Login error:', error);
throw error;
}
};
const logout = () => {
authService.logout();
console.log('🚪 Logging out user');
removeStoredToken();
setUser(null);
console.log('✅ Logout completed');
};
const refreshUser = () => {
setRefreshTrigger(prev => prev + 1);
const hasRole = (roles: string[]): boolean => {
if (!user) return false;
return roles.includes(user.role);
};
const hasRole = (roles: string[]) => {
return user ? roles.includes(user.role) : false;
};
useEffect(() => {
const initializeAuth = async () => {
console.log('🚀 Initializing authentication...');
await checkSetupStatus();
await refreshUser();
};
const value = {
initializeAuth();
}, []);
const value: AuthContextType = {
user,
login,
logout,
@@ -118,9 +151,18 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
loading,
refreshUser,
needsSetup,
checkSetupStatus
checkSetupStatus,
};
useEffect(() => {
console.log('🔄 Auth state changed - user:', user, 'loading:', loading);
}, [user, loading]);
useEffect(() => {
const token = getStoredToken();
console.log('💾 Stored token on mount:', token ? 'Exists' : 'None');
}, []);
return (
<AuthContext.Provider value={value}>
{children}
@@ -128,7 +170,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
);
};
export const useAuth = () => {
export const useAuth = (): AuthContextType => {
const context = useContext(AuthContext);
if (context === undefined) {
throw new Error('useAuth must be used within an AuthProvider');

View File

@@ -64,7 +64,6 @@ const Setup: React.FC = () => {
setLoading(true);
setError('');
// Create the request payload
const payload = {
password: formData.password,
name: formData.name,
@@ -72,7 +71,7 @@ const Setup: React.FC = () => {
...(formData.department ? { department: formData.department } : {})
};
console.log('Sending setup request with payload:', payload);
console.log('🚀 Sending setup request with payload:', payload);
const response = await fetch('http://localhost:3002/api/setup/admin', {
method: 'POST',
@@ -82,41 +81,38 @@ const Setup: React.FC = () => {
body: JSON.stringify(payload),
});
const responseText = await response.text();
console.log('📨 Setup response:', responseText);
let result;
try {
result = JSON.parse(responseText);
} catch (parseError) {
console.error('❌ Failed to parse response as JSON:', responseText);
throw new Error('Invalid server response');
}
if (!response.ok) {
const data = await response.json();
throw new Error(data.error || 'Setup fehlgeschlagen');
throw new Error(result.error || 'Setup fehlgeschlagen');
}
// Check response format
const contentType = response.headers.get('content-type');
if (!contentType || !contentType.includes('application/json')) {
const text = await response.text();
console.error('Non-JSON response:', text);
throw new Error('Server returned non-JSON response');
}
console.log('✅ Setup successful:', result);
const result = await response.json();
console.log('Setup response:', result);
// Re-check setup status after successful setup
// WICHTIG: Setup Status neu prüfen und dann zu Login navigieren
await checkSetupStatus();
// Automatically log in after setup
await login({
email: 'admin@instandhaltung.de',
password: formData.password
});
navigate('/');
// Kurze Verzögerung damit der State aktualisiert werden kann
setTimeout(() => {
navigate('/login');
}, 100);
} catch (err: any) {
console.error('Setup error:', err);
console.error('Setup error:', err);
setError(typeof err === 'string' ? err : err.message || 'Ein unerwarteter Fehler ist aufgetreten');
} finally {
setLoading(false);
}
};
return (
<div className="min-h-screen bg-gray-100 flex items-center justify-center">
<div className="max-w-md w-full bg-white rounded-lg shadow-lg p-8">

View File

@@ -1,127 +1,110 @@
// frontend/src/services/employeeService.ts
import { Employee, Availability, CreateEmployeeRequest, UpdateEmployeeRequest } from '../types/employee';
import { authService } from './authService';
import { Employee, CreateEmployeeRequest, UpdateEmployeeRequest, Availability } from '../types/employee';
const API_BASE = 'http://localhost:3002/api/employees';
const API_BASE_URL = 'http://localhost:3002/api';
const getAuthHeaders = () => {
const token = localStorage.getItem('token');
return {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
};
};
export class EmployeeService {
// Alle Mitarbeiter abrufen
async getEmployees(): Promise<Employee[]> {
const response = await fetch(`${API_BASE}?_=${Date.now()}`, {
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'no-cache',
'Pragma': 'no-cache',
...authService.getAuthHeaders()
}
const response = await fetch(`${API_BASE_URL}/employees`, {
headers: getAuthHeaders(),
});
if (!response.ok) {
throw new Error('Fehler beim Laden der Mitarbeiter');
throw new Error('Failed to fetch employees');
}
return response.json();
}
// Einzelnen Mitarbeiter abrufen
async getEmployee(id: string): Promise<Employee> {
const response = await fetch(`${API_BASE}/${id}`, {
headers: {
'Content-Type': 'application/json',
...authService.getAuthHeaders()
}
const response = await fetch(`${API_BASE_URL}/employees/${id}`, {
headers: getAuthHeaders(),
});
if (!response.ok) {
throw new Error('Mitarbeiter nicht gefunden');
throw new Error('Failed to fetch employee');
}
return response.json();
}
// Neuen Mitarbeiter erstellen
async createEmployee(employeeData: CreateEmployeeRequest): Promise<Employee> {
const response = await fetch(API_BASE, {
async createEmployee(employee: CreateEmployeeRequest): Promise<Employee> {
const response = await fetch(`${API_BASE_URL}/employees`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...authService.getAuthHeaders()
},
body: JSON.stringify(employeeData)
headers: getAuthHeaders(),
body: JSON.stringify(employee),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.error || 'Fehler beim Erstellen des Mitarbeiters');
throw new Error(error.error || 'Failed to create employee');
}
return response.json();
}
// Mitarbeiter aktualisieren
async updateEmployee(id: string, updates: UpdateEmployeeRequest): Promise<Employee> {
const response = await fetch(`${API_BASE}/${id}`, {
async updateEmployee(id: string, employee: UpdateEmployeeRequest): Promise<Employee> {
const response = await fetch(`${API_BASE_URL}/employees/${id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
...authService.getAuthHeaders()
},
body: JSON.stringify(updates)
headers: getAuthHeaders(),
body: JSON.stringify(employee),
});
if (!response.ok) {
throw new Error('Fehler beim Aktualisieren des Mitarbeiters');
const error = await response.json();
throw new Error(error.error || 'Failed to update employee');
}
return response.json();
}
// Mitarbeiter permanent löschen
async deleteEmployee(id: string): Promise<void> {
const response = await fetch(`${API_BASE}/${id}`, {
const response = await fetch(`${API_BASE_URL}/employees/${id}`, {
method: 'DELETE',
headers: {
...authService.getAuthHeaders()
}
headers: getAuthHeaders(),
});
if (!response.ok) {
const error = await response.json().catch(() => ({ error: 'Fehler beim Löschen des Mitarbeiters' }));
throw new Error(error.error || 'Fehler beim Löschen des Mitarbeiters');
const error = await response.json();
throw new Error(error.error || 'Failed to delete employee');
}
}
// Verfügbarkeiten abrufen
async getAvailabilities(employeeId: string): Promise<Availability[]> {
const response = await fetch(`${API_BASE}/${employeeId}/availabilities`, {
headers: {
'Content-Type': 'application/json',
...authService.getAuthHeaders()
}
const response = await fetch(`${API_BASE_URL}/employees/${employeeId}/availabilities`, {
headers: getAuthHeaders(),
});
if (!response.ok) {
throw new Error('Fehler beim Laden der Verfügbarkeiten');
throw new Error('Failed to fetch availabilities');
}
return response.json();
}
// Verfügbarkeiten aktualisieren
async updateAvailabilities(employeeId: string, availabilities: Availability[]): Promise<Availability[]> {
const response = await fetch(`${API_BASE}/${employeeId}/availabilities`, {
const response = await fetch(`${API_BASE_URL}/employees/${employeeId}/availabilities`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
...authService.getAuthHeaders()
},
body: JSON.stringify(availabilities)
headers: getAuthHeaders(),
body: JSON.stringify(availabilities),
});
if (!response.ok) {
throw new Error('Fehler beim Aktualisieren der Verfügbarkeiten');
const error = await response.json();
throw new Error(error.error || 'Failed to update availabilities');
}
return response.json();
}
};
}
// ✅ Exportiere eine Instanz der Klasse
export const employeeService = new EmployeeService();

View File

@@ -0,0 +1,15 @@
// frontend/src/types/user.ts
export interface User {
id: string;
email: string;
name: string;
role: 'admin' | 'instandhalter' | 'user';
phone?: string;
department?: string;
lastLogin?: string;
}
export interface LoginRequest {
email: string;
password: string;
}