Skip to main content
Glama
Login.tsx5.56 kB
import type { FormEvent } from 'react'; import { useState } from 'react'; import { useNavigate } from 'react-router-dom'; import axios from 'axios'; import { LogIn, AlertCircle, Shield, Eye, EyeOff } from 'lucide-react'; const API_BASE = import.meta.env.VITE_API_URL || 'http://localhost:3000'; interface AdminUser { id: string; username: string; email?: string; display_name?: string; role: string; } interface LoginProps { onLogin: (token: string, user: AdminUser) => void; } export default function Login({ onLogin }: LoginProps) { const navigate = useNavigate(); const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); const [showPassword, setShowPassword] = useState(false); const [error, setError] = useState<string | null>(null); const [loading, setLoading] = useState(false); const handleSubmit = async (e: FormEvent) => { e.preventDefault(); setError(null); setLoading(true); try { const response = await axios.post(`${API_BASE}/v1/auth/login`, { username, password, }); if (response.data.success) { // Store token localStorage.setItem('auth_token', response.data.token); localStorage.setItem('auth_user', JSON.stringify(response.data.user)); // Notify parent onLogin(response.data.token, response.data.user); // Redirect to dashboard navigate('/'); } else { setError(response.data.error || 'Login failed'); } } catch (err: unknown) { const error = err as { response?: { data?: { error?: string } } }; setError(error.response?.data?.error || 'Failed to connect to server'); } finally { setLoading(false); } }; return ( <div className="min-h-screen bg-slate-900 flex items-center justify-center p-4"> <div className="w-full max-w-md"> {/* Logo / Header */} <div className="text-center mb-8"> <div className="inline-flex items-center justify-center w-16 h-16 bg-blue-600 rounded-full mb-4"> <Shield className="w-8 h-8 text-white" /> </div> <h1 className="text-2xl font-bold text-white">AI MCP Gateway</h1> <p className="text-slate-400 mt-2">Admin Dashboard</p> </div> {/* Login Form */} <div className="bg-slate-800 rounded-lg shadow-xl p-6"> <h2 className="text-xl font-semibold text-white mb-6 flex items-center gap-2"> <LogIn className="w-5 h-5" /> Sign In </h2> {error && ( <div className="bg-red-900/50 border border-red-500 rounded-lg p-4 mb-4 flex items-center gap-2 text-red-200"> <AlertCircle className="w-5 h-5 flex-shrink-0" /> <span>{error}</span> </div> )} <form onSubmit={handleSubmit} className="space-y-4"> <div> <label className="block text-sm font-medium text-slate-300 mb-1"> Username </label> <input type="text" value={username} onChange={(e) => setUsername(e.target.value)} placeholder="Enter username" className="w-full px-4 py-2 bg-slate-700 border border-slate-600 rounded-lg text-white placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" required disabled={loading} autoFocus /> </div> <div> <label className="block text-sm font-medium text-slate-300 mb-1"> Password </label> <div className="relative"> <input type={showPassword ? 'text' : 'password'} value={password} onChange={(e) => setPassword(e.target.value)} placeholder="Enter password" className="w-full px-4 py-2 bg-slate-700 border border-slate-600 rounded-lg text-white placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent pr-10" required disabled={loading} /> <button type="button" onClick={() => setShowPassword(!showPassword)} className="absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 hover:text-white" > {showPassword ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />} </button> </div> </div> <button type="submit" disabled={loading} className="w-full py-2 px-4 bg-blue-600 hover:bg-blue-700 disabled:bg-blue-600/50 text-white font-medium rounded-lg flex items-center justify-center gap-2 transition-colors" > {loading ? ( <> <div className="w-4 h-4 border-2 border-white/30 border-t-white rounded-full animate-spin" /> Signing in... </> ) : ( <> <LogIn className="w-4 h-4" /> Sign In </> )} </button> </form> </div> {/* Footer */} <p className="text-center text-slate-500 text-sm mt-6"> Default credentials: admin / admin123 </p> </div> </div> ); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/babasida246/ai-mcp-gateway'

If you have feedback or need assistance with the MCP directory API, please join our Discord server