207 lines
7.9 KiB
TypeScript
207 lines
7.9 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
|
import {
|
|
Play, Shield, LogOut, ArrowRight, Lock, Mail, RefreshCw,
|
|
Layers, ShieldAlert, Users, GraduationCap, DollarSign, Database
|
|
} from 'lucide-react';
|
|
import AdminDashboard from './components/AdminDashboard';
|
|
import AdminContentManager from './components/AdminContentManager';
|
|
|
|
export default function AdminApp() {
|
|
const [token, setToken] = useState<string | null>(localStorage.getItem('admin_token'));
|
|
const [adminUser, setAdminUser] = useState<{
|
|
name: string;
|
|
email: string;
|
|
role: 'admin';
|
|
} | null>(null);
|
|
|
|
// App routing inside admin panel
|
|
const [activeView, setView] = useState<'dashboard' | 'courses'>('dashboard');
|
|
|
|
// Authentication forms
|
|
const [loginEmail, setLoginEmail] = useState('');
|
|
const [loginPassword, setLoginPassword] = useState('');
|
|
const [authError, setAuthError] = useState<string | null>(null);
|
|
const [authLoading, setAuthLoading] = useState(false);
|
|
const [settings, setSettings] = useState<{ brandName?: string; brandSlogan?: string; cnpj?: string; address?: string } | null>(null);
|
|
|
|
useEffect(() => {
|
|
fetch('/api/settings')
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
setSettings(data);
|
|
if (data.brandName) {
|
|
document.title = `${data.brandName}FLIX - Admin Portal`;
|
|
}
|
|
})
|
|
.catch(err => console.error('Error fetching settings:', err));
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (token) {
|
|
fetchAdminProfile();
|
|
}
|
|
}, [token]);
|
|
|
|
const fetchAdminProfile = async () => {
|
|
try {
|
|
const res = await fetch('/api/auth/me', {
|
|
headers: {
|
|
'Authorization': `Bearer ${token}`
|
|
}
|
|
});
|
|
if (res.ok) {
|
|
const data = await res.json();
|
|
if (data.role === 'admin') {
|
|
setAdminUser(data);
|
|
} else {
|
|
// If a student tries to use the admin token
|
|
setAuthError('Token inválido para acesso administrativo.');
|
|
handleLogout();
|
|
}
|
|
} else {
|
|
handleLogout();
|
|
}
|
|
} catch (err) {
|
|
console.error('Failed to load profile:', err);
|
|
handleLogout();
|
|
}
|
|
};
|
|
|
|
const handleLogin = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
setAuthError(null);
|
|
setAuthLoading(true);
|
|
|
|
try {
|
|
const res = await fetch('/api/auth/login', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ email: loginEmail, password: loginPassword })
|
|
});
|
|
const data = await res.json();
|
|
|
|
if (!res.ok) {
|
|
throw new Error(data.error || 'Erro ao efetuar login');
|
|
}
|
|
|
|
if (data.user.role !== 'admin') {
|
|
throw new Error('Acesso recusado. Apenas administradores podem acessar esta área.');
|
|
}
|
|
|
|
localStorage.setItem('admin_token', data.token);
|
|
setToken(data.token);
|
|
setAdminUser(data.user);
|
|
setLoginEmail('');
|
|
setLoginPassword('');
|
|
} catch (err: any) {
|
|
setAuthError(err.message);
|
|
} finally {
|
|
setAuthLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleLogout = () => {
|
|
localStorage.removeItem('admin_token');
|
|
setToken(null);
|
|
setAdminUser(null);
|
|
};
|
|
|
|
const quickFillAdmin = () => {
|
|
setLoginEmail('sidney.gomes1989@gmail.com');
|
|
setLoginPassword('admin123');
|
|
setAuthError(null);
|
|
};
|
|
|
|
return (
|
|
<div className="min-h-screen bg-[#0b0c10] text-zinc-100 flex flex-col selection:bg-[#E50914] selection:text-white font-sans">
|
|
{token && adminUser ? (
|
|
<AdminDashboard token={token} adminUser={adminUser} onLogout={handleLogout} />
|
|
) : (
|
|
/* --- UNAUTHENTICATED ADMIN ACCESS SCREEN --- */
|
|
<div className="flex-grow flex items-center justify-center p-4 md:p-8 relative" style={{ backgroundImage: `linear-gradient(rgba(11,12,16,0.9), rgba(11,12,16,0.9)), url('https://images.unsplash.com/photo-1558494949-ef010cbdcc31?q=80&w=1200&auto=format&fit=crop')`, backgroundSize: 'cover', backgroundPosition: 'center' }}>
|
|
<div className="absolute inset-0 bg-black/40 z-0" />
|
|
|
|
{/* Glass login panel */}
|
|
<div className="relative z-10 w-full max-w-md glass-card rounded-2xl p-6 md:p-10 space-y-6 shadow-2xl border border-white/10">
|
|
|
|
{/* Admin Header Accent */}
|
|
<div className="flex flex-col items-center space-y-3 text-center">
|
|
<div className="bg-[#E50914] text-white p-3 rounded-xl flex items-center justify-center shadow-lg animate-pulse">
|
|
<Shield className="w-7 h-7" />
|
|
</div>
|
|
<div className="space-y-1">
|
|
<h1 className="font-display font-black text-2xl tracking-tighter text-white">
|
|
{(settings?.brandName || 'Plataforma')}<span className="text-[#E50914]">FLIX</span> <span className="text-zinc-500 font-medium text-lg">ADMIN</span>
|
|
</h1>
|
|
<p className="text-[11px] text-zinc-400 font-medium">Autenticação obrigatória para gestores, administradores e moderadores.</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Error notifications */}
|
|
{authError && (
|
|
<div className="bg-red-950/40 border border-red-900/50 text-red-400 p-3.5 rounded-xl text-xs leading-relaxed text-center flex items-center justify-center space-x-2">
|
|
<ShieldAlert className="w-4 h-4 flex-shrink-0 text-red-500" />
|
|
<span>{authError}</span>
|
|
</div>
|
|
)}
|
|
|
|
{/* Form */}
|
|
<form onSubmit={handleLogin} className="space-y-4">
|
|
<div className="space-y-1">
|
|
<label className="text-[10px] text-zinc-400 font-bold uppercase tracking-wider">E-mail Administrativo</label>
|
|
<div className="relative">
|
|
<Mail className="absolute left-3.5 top-3.5 w-4 h-4 text-zinc-500" />
|
|
<input
|
|
type="email"
|
|
required
|
|
value={loginEmail}
|
|
onChange={(e) => setLoginEmail(e.target.value)}
|
|
placeholder="admin@email.com"
|
|
className="w-full glass-input rounded-xl p-3.5 pl-11 text-xs text-white focus:outline-none"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-1">
|
|
<label className="text-[10px] text-zinc-400 font-bold uppercase tracking-wider">Senha de Segurança</label>
|
|
<div className="relative">
|
|
<Lock className="absolute left-3.5 top-3.5 w-4 h-4 text-zinc-500" />
|
|
<input
|
|
type="password"
|
|
required
|
|
value={loginPassword}
|
|
onChange={(e) => setLoginPassword(e.target.value)}
|
|
placeholder="Sua senha privada"
|
|
className="w-full glass-input rounded-xl p-3.5 pl-11 text-xs text-white focus:outline-none"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<button
|
|
type="submit"
|
|
disabled={authLoading}
|
|
className="w-full glass-btn-primary text-white text-xs font-bold py-3.5 px-4 rounded-xl flex items-center justify-center space-x-2 cursor-pointer disabled:opacity-50"
|
|
>
|
|
<span>{authLoading ? 'Autenticando...' : 'Acessar Painel de Controle'}</span>
|
|
<ArrowRight className="w-4 h-4" />
|
|
</button>
|
|
|
|
{/* SANDBOX ACCESS PRESET */}
|
|
<div className="border-t border-white/5 pt-4">
|
|
<button
|
|
type="button"
|
|
onClick={quickFillAdmin}
|
|
className="w-full bg-white/5 hover:bg-white/10 text-zinc-300 font-bold p-2.5 rounded-lg text-xs transition-colors flex items-center justify-center space-x-1"
|
|
>
|
|
<Users className="w-4 h-4 text-[#E50914]" />
|
|
<span>Entrar com Credencial Admin Demonstrativa</span>
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|