280 lines
12 KiB
TypeScript
280 lines
12 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('devflix_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);
|
|
|
|
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('devflix_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('devflix_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 ? (
|
|
/* --- AUTHENTICATED ADMIN AREA --- */
|
|
<>
|
|
{/* Admin Header / Navbar */}
|
|
<header className="sticky top-0 z-50 glass-navbar px-4 py-4 md:px-8 flex items-center justify-between border-b border-white/5">
|
|
<div className="flex items-center space-x-8">
|
|
{/* Logo with Admin Tag */}
|
|
<div className="flex items-center space-x-2.5 cursor-pointer select-none">
|
|
<div className="bg-[#E50914] text-white p-1.5 rounded flex items-center justify-center">
|
|
<Shield className="w-5 h-5" />
|
|
</div>
|
|
<div className="flex flex-col">
|
|
<span className="font-display font-black text-lg md:text-xl leading-none tracking-tighter text-[#E50914]">
|
|
MICROTEC<span className="text-white">FLIX</span>
|
|
</span>
|
|
<span className="text-[9px] text-[#E50914] font-extrabold uppercase tracking-widest mt-0.5 leading-none">
|
|
Admin Portal
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Navigation Tabs */}
|
|
<nav className="hidden md:flex items-center space-x-6 text-sm">
|
|
<button
|
|
onClick={() => setView('dashboard')}
|
|
className={`font-semibold transition-all hover:text-white flex items-center space-x-2 px-3 py-1.5 rounded-lg ${activeView === 'dashboard' ? 'text-white bg-white/5' : 'text-zinc-400'}`}
|
|
>
|
|
<Database className="w-4 h-4 text-[#E50914]" />
|
|
<span>Painel Geral</span>
|
|
</button>
|
|
<button
|
|
onClick={() => setView('courses')}
|
|
className={`font-semibold transition-all hover:text-white flex items-center space-x-2 px-3 py-1.5 rounded-lg ${activeView === 'courses' ? 'text-white bg-white/5' : 'text-zinc-400'}`}
|
|
>
|
|
<GraduationCap className="w-4 h-4 text-[#E50914]" />
|
|
<span>Grade de Cursos</span>
|
|
</button>
|
|
</nav>
|
|
</div>
|
|
|
|
{/* Admin Right Menu */}
|
|
<div className="flex items-center space-x-4">
|
|
<div className="text-right hidden sm:block border-r border-white/10 pr-4">
|
|
<p className="text-xs font-bold text-white">{adminUser.name}</p>
|
|
<p className="text-[10px] text-[#E50914] font-black uppercase tracking-wider">{adminUser.email}</p>
|
|
</div>
|
|
|
|
{/* Mobile View Toggle Buttons */}
|
|
<div className="flex md:hidden items-center bg-white/5 p-1 rounded-lg">
|
|
<button
|
|
onClick={() => setView('dashboard')}
|
|
className={`text-xs font-bold px-2.5 py-1.5 rounded-md ${activeView === 'dashboard' ? 'bg-[#E50914] text-white' : 'text-zinc-400'}`}
|
|
>
|
|
Geral
|
|
</button>
|
|
<button
|
|
onClick={() => setView('courses')}
|
|
className={`text-xs font-bold px-2.5 py-1.5 rounded-md ${activeView === 'courses' ? 'bg-[#E50914] text-white' : 'text-zinc-400'}`}
|
|
>
|
|
Grade
|
|
</button>
|
|
</div>
|
|
|
|
<button
|
|
onClick={handleLogout}
|
|
title="Sair do Painel Admin"
|
|
className="w-9 h-9 rounded-full bg-white/5 hover:bg-red-950/50 text-zinc-400 hover:text-red-400 flex items-center justify-center transition-all cursor-pointer"
|
|
>
|
|
<LogOut className="w-4 h-4" />
|
|
</button>
|
|
</div>
|
|
</header>
|
|
|
|
{/* Main workspace */}
|
|
<main className="flex-grow p-4 md:p-8 max-w-7xl mx-auto w-full space-y-8">
|
|
{activeView === 'dashboard' ? (
|
|
<AdminDashboard token={token} />
|
|
) : (
|
|
<AdminContentManager token={token} />
|
|
)}
|
|
</main>
|
|
|
|
{/* Admin Footer */}
|
|
<footer className="border-t border-white/5 bg-[#08090d] py-6 px-4 text-center text-[11px] text-zinc-600">
|
|
<p className="font-bold uppercase tracking-wider text-zinc-500">MicrotecFlix Admin Control Center • Shared PostgreSQL Engine</p>
|
|
<p className="mt-1">Acesso corporativo restrito. Todas as auditorias e alterações de cursos e cobranças Asaas são registradas.</p>
|
|
</footer>
|
|
</>
|
|
) : (
|
|
/* --- 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">
|
|
MICROTEC<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="email.admin@devflix.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>
|
|
);
|
|
}
|