198 lines
7.2 KiB
JavaScript
198 lines
7.2 KiB
JavaScript
import { useState, useEffect } from 'react';
|
|
import { useNavigate } from 'react-router-dom';
|
|
import { useAuth } from '../../contexts/AuthContext';
|
|
import axios from 'axios';
|
|
|
|
export default function Login() {
|
|
const [email, setEmail] = useState(''); // Remove pre-filled email
|
|
const [password, setPassword] = useState(''); // Remove pre-filled password
|
|
const [error, setError] = useState('');
|
|
const [loading, setLoading] = useState(false);
|
|
const [authState, setAuthState] = useState({
|
|
hasToken: false,
|
|
hasUser: false,
|
|
tokenValue: null,
|
|
userValue: null
|
|
});
|
|
|
|
const navigate = useNavigate();
|
|
const { login, currentUser } = useAuth();
|
|
|
|
// Check and display current auth state for debugging
|
|
useEffect(() => {
|
|
const token = localStorage.getItem('token');
|
|
const user = localStorage.getItem('user');
|
|
|
|
try {
|
|
setAuthState({
|
|
hasToken: !!token,
|
|
hasUser: !!user,
|
|
tokenValue: token ? `${token.substring(0, 10)}...` : null,
|
|
userValue: user ? JSON.parse(user) : null
|
|
});
|
|
|
|
console.log('Login page loaded with auth state:', {
|
|
hasToken: !!token,
|
|
hasUser: !!user,
|
|
currentUser
|
|
});
|
|
} catch (error) {
|
|
console.error('Error parsing user data:', error);
|
|
}
|
|
}, [currentUser]);
|
|
|
|
const handleSubmit = async (e) => {
|
|
e.preventDefault();
|
|
|
|
try {
|
|
setError('');
|
|
setLoading(true);
|
|
|
|
console.log('Attempting login with:', { email, password: '***' });
|
|
|
|
// Try direct API call first
|
|
try {
|
|
const response = await axios.post('/api/auth/login', { email, password });
|
|
console.log('Login response:', response.data);
|
|
|
|
// Check if the response has the expected structure
|
|
if (response.data && response.data.success === true) {
|
|
const { token, user } = response.data;
|
|
|
|
console.log('Extracted token and user:', { tokenExists: !!token, user });
|
|
|
|
// Save token to localStorage
|
|
localStorage.setItem('token', token);
|
|
|
|
// Set axios default headers for future requests
|
|
axios.defaults.headers.common['Authorization'] = `Bearer ${token}`;
|
|
|
|
// Update auth context manually
|
|
localStorage.setItem('user', JSON.stringify(user));
|
|
|
|
console.log('Login successful, redirecting based on role:', user.role);
|
|
|
|
// Force reload to ensure auth context is properly updated
|
|
window.location.href = '/dashboard';
|
|
return;
|
|
} else {
|
|
console.error('Unexpected response structure:', response.data);
|
|
throw new Error('Invalid response structure from server');
|
|
}
|
|
} catch (directApiError) {
|
|
console.error('Direct API call failed:', directApiError);
|
|
console.error('Error details:', directApiError.response ? directApiError.response.data : 'No response data');
|
|
|
|
// If the error is a 401, show appropriate message
|
|
if (directApiError.response?.status === 401) {
|
|
setError('Invalid email or password');
|
|
return;
|
|
}
|
|
|
|
throw directApiError;
|
|
}
|
|
} catch (error) {
|
|
setError('Failed to log in. Please check your credentials.');
|
|
console.error('Login error:', error);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-blue-50 via-white to-purple-50 py-12 px-4 sm:px-6 lg:px-8">
|
|
<div className="max-w-md w-full space-y-8 animate-fadeIn">
|
|
{/* Logo and Title */}
|
|
<div className="text-center">
|
|
<div className="mx-auto h-20 w-20 bg-gradient-to-br from-blue-600 to-purple-600 rounded-2xl flex items-center justify-center shadow-2xl">
|
|
<span className="text-white text-3xl font-bold">II</span>
|
|
</div>
|
|
<h2 className="mt-6 text-4xl font-extrabold text-gray-900 tracking-tight">
|
|
InInventer
|
|
</h2>
|
|
<p className="mt-2 text-base text-gray-600">
|
|
Sign in to manage your inventory
|
|
</p>
|
|
</div>
|
|
|
|
{/* Login Form */}
|
|
<div className="mt-8 bg-white rounded-2xl shadow-2xl overflow-hidden">
|
|
<div className="px-8 py-10">
|
|
{error && (
|
|
<div className="alert alert-error mb-6 animate-fadeIn">
|
|
<svg className="w-5 h-5 flex-shrink-0" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clipRule="evenodd" />
|
|
</svg>
|
|
<span>{error}</span>
|
|
</div>
|
|
)}
|
|
|
|
<form className="space-y-6" onSubmit={handleSubmit}>
|
|
<div>
|
|
<label htmlFor="email" className="form-label">
|
|
Email address
|
|
</label>
|
|
<input
|
|
id="email"
|
|
name="email"
|
|
type="email"
|
|
autoComplete="email"
|
|
required
|
|
value={email}
|
|
onChange={(e) => setEmail(e.target.value)}
|
|
className="form-input"
|
|
placeholder="Enter your email"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label htmlFor="password" className="form-label">
|
|
Password
|
|
</label>
|
|
<input
|
|
id="password"
|
|
name="password"
|
|
type="password"
|
|
autoComplete="current-password"
|
|
required
|
|
value={password}
|
|
onChange={(e) => setPassword(e.target.value)}
|
|
className="form-input"
|
|
placeholder="Enter your password"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<button
|
|
type="submit"
|
|
disabled={loading}
|
|
className="w-full btn btn-primary text-base py-3"
|
|
>
|
|
{loading ? (
|
|
<div className="flex items-center justify-center">
|
|
<svg className="animate-spin -ml-1 mr-3 h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
|
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
|
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
</svg>
|
|
Signing in...
|
|
</div>
|
|
) : (
|
|
'Sign in'
|
|
)}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
{/* Footer */}
|
|
<div className="px-8 py-4 bg-gray-50 border-t border-gray-100">
|
|
<p className="text-xs text-center text-gray-500">
|
|
© 2025 InInventer. All rights reserved.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|