Files
NeveTimePanel/frontend/src/components/Auth.jsx

198 lines
7.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState, useEffect } from 'react';
import { Server, Eye, EyeOff } from 'lucide-react';
import { getTheme } from '../themes';
import { API_URL } from '../config';
import axios from 'axios';
export default function Auth({ onLogin }) {
const [isLogin, setIsLogin] = useState(true);
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [showPassword, setShowPassword] = useState(false);
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const [theme] = useState(localStorage.getItem('theme') || 'modern');
const [oidcProviders, setOidcProviders] = useState({});
const currentTheme = getTheme(theme);
useEffect(() => {
loadOidcProviders();
}, []);
const loadOidcProviders = async () => {
try {
const { data } = await axios.get(`${API_URL}/api/auth/oidc/providers`);
setOidcProviders(data);
} catch (error) {
console.error('Ошибка загрузки OIDC провайдеров:', error);
}
};
const handleOidcLogin = (provider) => {
window.location.href = `${API_URL}/api/auth/oidc/${provider}/login`;
};
const handleSubmit = async (e) => {
e.preventDefault();
setError('');
setLoading(true);
try {
await onLogin(username, password, isLogin);
} catch (err) {
setError(err.message || 'Ошибка авторизации');
} finally {
setLoading(false);
}
};
return (
<div className={`min-h-screen ${currentTheme.primary} flex items-center justify-center p-4 transition-colors duration-300`}>
<div className="w-full max-w-md">
{/* Logo */}
<div className="text-center mb-8">
<div className={`${currentTheme.accent} w-16 h-16 rounded-2xl flex items-center justify-center mx-auto mb-4 shadow-lg`}>
<Server className="w-10 h-10 text-white" />
</div>
<h1 className={`text-3xl font-bold bg-gradient-to-r ${currentTheme.gradient} bg-clip-text text-transparent mb-2`}>MC Panel</h1>
<p className={`${currentTheme.textSecondary}`}>Панель управления Minecraft серверами</p>
</div>
{/* Form Card */}
<div className={`${currentTheme.secondary} rounded-2xl shadow-2xl ${currentTheme.border} border p-8`}>
{/* Tabs */}
<div className="flex gap-2 mb-6">
<button
onClick={() => setIsLogin(true)}
className={`flex-1 py-3 rounded-xl font-medium transition-all duration-200 ${
isLogin
? `${currentTheme.accent} text-white shadow-lg`
: `${currentTheme.card} ${currentTheme.text} ${currentTheme.hover}`
}`}
>
Вход
</button>
<button
onClick={() => setIsLogin(false)}
className={`flex-1 py-3 rounded-xl font-medium transition-all duration-200 ${
!isLogin
? `${currentTheme.accent} text-white shadow-lg`
: `${currentTheme.card} ${currentTheme.text} ${currentTheme.hover}`
}`}
>
Регистрация
</button>
</div>
<form onSubmit={handleSubmit} className="space-y-5">
{/* Username */}
<div>
<label className={`block text-sm font-medium ${currentTheme.text} mb-2`}>
Имя пользователя
</label>
<input
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
required
className={`w-full ${currentTheme.input} ${currentTheme.border} border rounded-xl px-4 py-3 ${currentTheme.text} focus:outline-none focus:ring-2 focus:ring-blue-500 transition`}
placeholder="admin"
/>
</div>
{/* Password */}
<div>
<label className={`block text-sm font-medium ${currentTheme.text} mb-2`}>
Пароль
</label>
<div className="relative">
<input
type={showPassword ? 'text' : 'password'}
value={password}
onChange={(e) => setPassword(e.target.value)}
required
className={`w-full ${currentTheme.input} ${currentTheme.border} border rounded-xl px-4 py-3 pr-12 ${currentTheme.text} focus:outline-none focus:ring-2 focus:ring-blue-500 transition`}
placeholder="••••••••"
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className={`absolute right-3 top-1/2 -translate-y-1/2 ${currentTheme.textSecondary} hover:${currentTheme.text} transition`}
>
{showPassword ? <EyeOff className="w-5 h-5" /> : <Eye className="w-5 h-5" />}
</button>
</div>
</div>
{/* Error */}
{error && (
<div className="bg-red-500 bg-opacity-10 border border-red-500 rounded-xl p-3 text-red-400 text-sm">
{error}
</div>
)}
{/* Submit Button */}
<button
type="submit"
disabled={loading}
className={`w-full ${currentTheme.accent} ${currentTheme.accentHover} text-white py-3 rounded-xl font-medium disabled:opacity-50 disabled:cursor-not-allowed transition-all duration-200 shadow-lg hover:shadow-xl`}
>
{loading ? (
<span className="flex items-center justify-center gap-2">
<div className="w-5 h-5 border-2 border-white border-t-transparent rounded-full animate-spin" />
Загрузка...
</span>
) : (
isLogin ? 'Войти' : 'Зарегистрироваться'
)}
</button>
</form>
{/* OpenID Connect Providers */}
{Object.keys(oidcProviders).length > 0 && (
<div className="mt-6">
<div className="relative">
<div className="absolute inset-0 flex items-center">
<div className={`w-full border-t ${currentTheme.border}`} />
</div>
<div className="relative flex justify-center text-sm">
<span className={`${currentTheme.secondary} px-2 ${currentTheme.textSecondary}`}>
Или войдите через
</span>
</div>
</div>
<div className="mt-6 grid gap-3">
{Object.entries(oidcProviders).map(([providerId, provider]) => (
<button
key={providerId}
onClick={() => handleOidcLogin(providerId)}
className={`w-full flex justify-center items-center px-4 py-3 border border-transparent rounded-xl text-sm font-medium text-white ${provider.color} transition-colors duration-200 shadow-sm hover:shadow-md`}
>
<span className="mr-2 text-lg">{provider.icon}</span>
Войти через {provider.name}
</button>
))}
</div>
</div>
)}
{/* Default Credentials */}
{isLogin && (
<div className={`mt-6 text-center text-sm ${currentTheme.textSecondary}`}>
<p>Учётные данные по умолчанию:</p>
<p className={`${currentTheme.text} font-mono mt-1`}>none / none</p>
</div>
)}
</div>
{/* Footer */}
<div className={`text-center mt-6 text-sm ${currentTheme.textSecondary}`}>
<p>© 2026 MC Panel. Все права защищены.</p>
</div>
</div>
</div>
);
}