Files
2026-02-18 16:50:48 +01:00

202 lines
8.2 KiB
JavaScript
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. 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 { useRouter } from 'next/router';
import Navbar from '../Navbar';
export default function Admin() {
const [token, setToken] = useState('');
const [authorized, setAuthorized] = useState(false);
const [tab, setTab] = useState('crear');
const [stats, setStats] = useState({ total: 0, top: [] });
const [form, setForm] = useState({ title: '', tags: '', content: '' });
const [posts, setPosts] = useState([]);
const router = useRouter();
useEffect(() => {
const t = localStorage.getItem('blog_token');
if (!t) {
router.push('/login');
} else {
setToken(t);
setAuthorized(true);
loadPosts();
loadStats(t);
}
}, []);
const loadPosts = () => {
fetch('http://localhost:9002/posts')
.then(r => r.json())
.then(setPosts)
.catch(e => console.error("Error cargando posts"));
};
const loadStats = (t) => {
fetch('http://localhost:9002/posts/stats', {
headers: { 'Authorization': `Bearer ${t || token}` }
})
.then(r => r.json())
.then(setStats)
.catch(e => console.error("Error en stats"));
};
const handleLogout = () => {
localStorage.removeItem('blog_token');
router.push('/login');
};
const handleSave = async (e) => {
e.preventDefault();
const res = await fetch('http://localhost:9002/posts', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify(form)
});
if (res.ok) {
alert('Publicado con éxito');
setForm({ title: '', tags: '', content: '' });
loadPosts();
}
};
const handleDelete = async (id) => {
if(!confirm('¿Estás seguro de eliminar este post?')) return;
await fetch(`http://localhost:9002/posts/${id}`, {
method: 'DELETE',
headers: { 'Authorization': `Bearer ${token}` }
});
loadPosts();
};
// Protección de renderizado
if (!authorized) {
return <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100vh', fontFamily: 'sans-serif' }}>
<h2 style={{ color: '#64748b' }}>Verificando credenciales...</h2>
</div>;
}
return (
<div style={{ backgroundColor: '#f8fafc', minHeight: '100vh', fontFamily: 'sans-serif', paddingBottom: '50px' }}>
<Navbar />
<div style={{ maxWidth: '1000px', margin: '0 auto', padding: '40px 20px' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '40px' }}>
<h1 style={{ color: '#0f172a', margin: 0, fontSize: '2rem' }}>Panel Administrativo</h1>
<button
onClick={handleLogout}
style={{ padding: '10px 20px', backgroundColor: '#ef4444', color: 'white', border: 'none', borderRadius: '8px', fontWeight: 'bold', cursor: 'pointer' }}
>
Cerrar Sesión 🔒
</button>
</div>
{/* NAVEGACIÓN */}
<div style={{ display: 'flex', gap: '10px', marginBottom: '30px' }}>
<button
onClick={() => setTab('crear')}
style={{
padding: '12px 24px', borderRadius: '10px', fontWeight: 'bold', cursor: 'pointer',
border: '2px solid #0f172a',
backgroundColor: tab === 'crear' ? '#0f172a' : 'transparent',
color: tab === 'crear' ? 'white' : '#0f172a'
}}
>
Crear Post
</button>
<button
onClick={() => setTab('lista')}
style={{
padding: '12px 24px', borderRadius: '10px', fontWeight: 'bold', cursor: 'pointer',
border: '2px solid #0f172a',
backgroundColor: tab === 'lista' ? '#0f172a' : 'transparent',
color: tab === 'lista' ? 'white' : '#0f172a'
}}
>
📋 Gestionar
</button>
<button
onClick={() => { setTab('stats'); loadStats(); }}
style={{
padding: '12px 24px', borderRadius: '10px', fontWeight: 'bold', cursor: 'pointer',
border: '2px solid #0f172a',
backgroundColor: tab === 'stats' ? '#0f172a' : 'transparent',
color: tab === 'stats' ? 'white' : '#0f172a'
}}
>
📊 Estadísticas
</button>
</div>
{/* CONTENIDO */}
{tab === 'crear' && (
<form onSubmit={handleSave} style={{ backgroundColor: 'white', padding: '30px', borderRadius: '16px', boxShadow: '0 4px 6px rgba(0,0,0,0.05)', border: '1px solid #e2e8f0' }}>
<input
placeholder="Título"
value={form.title}
onChange={e => setForm({...form, title: e.target.value})}
style={{ width: '100%', padding: '15px', marginBottom: '20px', borderRadius: '8px', border: '1px solid #e2e8f0', fontSize: '1rem', boxSizing: 'border-box' }}
required
/>
<input
placeholder="Tags"
value={form.tags}
onChange={e => setForm({...form, tags: e.target.value})}
style={{ width: '100%', padding: '15px', marginBottom: '20px', borderRadius: '8px', border: '1px solid #e2e8f0', fontSize: '1rem', boxSizing: 'border-box' }}
/>
<textarea
placeholder="Contenido (puedes usar HTML)"
value={form.content}
onChange={e => setForm({...form, content: e.target.value})}
style={{ width: '100%', padding: '15px', marginBottom: '20px', borderRadius: '8px', border: '1px solid #e2e8f0', fontSize: '1rem', height: '250px', boxSizing: 'border-box', fontFamily: 'inherit' }}
required
/>
<button style={{ width: '100%', padding: '15px', backgroundColor: '#2563eb', color: 'white', border: 'none', borderRadius: '8px', fontWeight: 'bold', fontSize: '1.1rem', cursor: 'pointer' }}>
Publicar Artículo
</button>
</form>
)}
{tab === 'lista' && (
<div style={{ backgroundColor: 'white', borderRadius: '16px', border: '1px solid #e2e8f0', overflow: 'hidden' }}>
{posts.map(p => (
<div key={p.id} style={{ display: 'flex', justifyContent: 'space-between', padding: '20px', borderBottom: '1px solid #f1f5f9', alignItems: 'center' }}>
<span style={{ fontWeight: '600', color: '#334155' }}>{p.title}</span>
<button
onClick={() => handleDelete(p.id)}
style={{ backgroundColor: '#fee2e2', color: '#ef4444', border: 'none', padding: '8px 15px', borderRadius: '6px', fontWeight: 'bold', cursor: 'pointer' }}
>
Eliminar
</button>
</div>
))}
</div>
)}
{tab === 'stats' && (
<div style={{ backgroundColor: 'white', padding: '30px', borderRadius: '16px', border: '1px solid #e2e8f0' }}>
<div style={{ textAlign: 'center', marginBottom: '40px', padding: '30px', backgroundColor: '#f0f9ff', borderRadius: '12px' }}>
<h3 style={{ margin: 0, color: '#0369a1' }}>Visitas Totales</h3>
<p style={{ fontSize: '3rem', fontWeight: '800', margin: '10px 0', color: '#0284c7' }}>{stats.total}</p>
</div>
<h4 style={{ marginBottom: '20px' }}>Ranking de Lectura</h4>
{stats.top.map((s, i) => (
<div key={i} style={{ marginBottom: '20px' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '8px' }}>
<span style={{ fontSize: '0.9rem', fontWeight: '600' }}>{s.title}</span>
<span style={{ fontSize: '0.9rem', color: '#2563eb', fontWeight: 'bold' }}>{s.views} views</span>
</div>
<div style={{ width: '100%', height: '8px', backgroundColor: '#f1f5f9', borderRadius: '4px', overflow: 'hidden' }}>
<div style={{ width: `${(s.views / (stats.total || 1)) * 100}%`, height: '100%', backgroundColor: '#2563eb' }}></div>
</div>
</div>
))}
</div>
)}
</div>
</div>
);
}