e045354892
Adds initial project structure, dependencies, and basic configuration for the License Tracker Pro v1.2 application. This includes setting up Vite for the build process, defining core types, and incorporating essential UI components like icons and a timeline. The project is now ready for development.
131 lines
6.6 KiB
TypeScript
131 lines
6.6 KiB
TypeScript
import React, { useState } from 'react';
|
|
import { login, getAzureLoginUrl } from '../graphService';
|
|
import { User, AppSettings } from '../types';
|
|
|
|
interface LoginFormProps {
|
|
settings: AppSettings;
|
|
onLoginSuccess: (user: User) => void;
|
|
onCancel: () => void;
|
|
}
|
|
|
|
const LoginForm: React.FC<LoginFormProps> = ({ settings, onLoginSuccess, onCancel }) => {
|
|
const [username, setUsername] = useState('');
|
|
const [password, setPassword] = useState('');
|
|
const [authSource, setAuthSource] = useState<'local' | 'ldap'>('local');
|
|
const [error, setError] = useState('');
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
|
|
const handleLogin = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
if (!username || !password) {
|
|
setError("Username and password are required.");
|
|
return;
|
|
}
|
|
setIsLoading(true);
|
|
setError('');
|
|
try {
|
|
const data = await login(username, password, authSource);
|
|
console.log("[LOGIN DEBUG] Server Response:", data);
|
|
|
|
if (data && (data.user || data.success === true)) {
|
|
// PHP might return {success: true, user: {...}} or just {user: {...}}
|
|
const userObj = data.user || data;
|
|
onLoginSuccess(userObj as User);
|
|
} else {
|
|
setError(data?.error || "Authentication failed: No user data returned.");
|
|
}
|
|
} catch (err: any) {
|
|
console.error("[LOGIN ERROR]", err);
|
|
setError(err.message || 'The server rejected these credentials.');
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleAzureLogin = async () => {
|
|
setIsLoading(true);
|
|
try {
|
|
const { url } = await getAzureLoginUrl();
|
|
window.location.href = url;
|
|
} catch (err: any) {
|
|
setError(err.message);
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
// Safety check for settings object
|
|
if (!settings) return null;
|
|
|
|
return (
|
|
<div className="fixed inset-0 bg-slate-950/80 flex justify-center items-center z-50 p-4 backdrop-blur-sm">
|
|
<div className="bg-slate-900 p-8 rounded-2xl shadow-2xl w-full max-w-sm border border-slate-800 animate-in fade-in zoom-in duration-200">
|
|
<div className="text-center mb-8">
|
|
<h2 className="text-2xl font-bold text-white mb-1">
|
|
System Login
|
|
</h2>
|
|
<p className="text-xs text-slate-500">Secure License Repository</p>
|
|
</div>
|
|
|
|
{error && (
|
|
<div className="bg-red-500/10 border border-red-500/50 text-red-400 text-[10px] font-mono p-3 rounded-lg mb-6 text-center break-words">
|
|
{error}
|
|
</div>
|
|
)}
|
|
|
|
<div className="space-y-4">
|
|
{settings?.azure_enabled && (
|
|
<button
|
|
onClick={handleAzureLogin}
|
|
disabled={isLoading}
|
|
className="w-full flex items-center justify-center gap-3 bg-white hover:bg-gray-100 text-slate-900 font-bold py-2.5 px-4 rounded-xl transition-all disabled:opacity-50"
|
|
>
|
|
<svg className="w-4 h-4" viewBox="0 0 21 21" xmlns="http://www.w3.org/2000/svg"><path d="m0 0h10.1v10.1h-10.1z" fill="#f35325"/><path d="m10.9 0h10.1v10.1h-10.1z" fill="#81bc06"/><path d="m0 10.9h10.1v10.1h-10.1z" fill="#05a6f0"/><path d="m10.9 10.9h10.1v10.1h-10.1z" fill="#ffba08"/></svg>
|
|
Entra ID SSO
|
|
</button>
|
|
)}
|
|
|
|
<form onSubmit={handleLogin} className="space-y-4">
|
|
{settings?.ldap_enabled && (
|
|
<div className="grid grid-cols-2 gap-1 p-1 bg-slate-950 rounded-lg border border-slate-800">
|
|
<button type="button" onClick={() => setAuthSource('local')} className={`py-1.5 text-xs font-bold rounded-md transition-all ${authSource === 'local' ? 'bg-slate-800 text-cyan-400' : 'text-slate-500'}`}>Local</button>
|
|
<button type="button" onClick={() => setAuthSource('ldap')} className={`py-1.5 text-xs font-bold rounded-md transition-all ${authSource === 'ldap' ? 'bg-slate-800 text-cyan-400' : 'text-slate-500'}`}>LDAP</button>
|
|
</div>
|
|
)}
|
|
|
|
<div>
|
|
<label className="block text-[10px] font-bold text-slate-500 uppercase mb-1 ml-1 tracking-widest">Username</label>
|
|
<input
|
|
type="text"
|
|
value={username}
|
|
onChange={e => setUsername(e.target.value)}
|
|
className="w-full bg-slate-950 border border-slate-800 rounded-xl p-3 text-sm text-gray-200 focus:outline-none focus:border-cyan-500 transition-all shadow-inner"
|
|
placeholder="Username"
|
|
autoFocus
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-[10px] font-bold text-slate-500 uppercase mb-1 ml-1 tracking-widest">Password</label>
|
|
<input
|
|
type="password"
|
|
value={password}
|
|
onChange={e => setPassword(e.target.value)}
|
|
className="w-full bg-slate-950 border border-slate-800 rounded-xl p-3 text-sm text-gray-200 focus:outline-none focus:border-cyan-500 transition-all shadow-inner"
|
|
placeholder="••••••••"
|
|
/>
|
|
</div>
|
|
|
|
<div className="flex gap-3 pt-2">
|
|
<button type="button" onClick={onCancel} className="flex-1 bg-slate-800 hover:bg-slate-700 text-slate-400 text-sm font-bold py-3 rounded-xl border border-slate-700 transition-all">Cancel</button>
|
|
<button type="submit" disabled={isLoading} className="flex-1 bg-cyan-600 hover:bg-cyan-500 text-white text-sm font-bold py-3 rounded-xl transition-all shadow-lg active:scale-95 disabled:opacity-50">
|
|
{isLoading ? 'Authenticating...' : 'Sign In'}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default LoginForm;
|