mirror of
https://github.com/arthur-pbty/links.git
synced 2026-08-01 20:29:27 +02:00
268 lines
7.5 KiB
JavaScript
268 lines
7.5 KiB
JavaScript
'use client';
|
|
|
|
import { useEffect, useMemo, useState } from 'react';
|
|
|
|
const initialForm = {
|
|
name: '',
|
|
url: '',
|
|
icon: '/github.png',
|
|
description: '',
|
|
category: 'Autres'
|
|
};
|
|
|
|
export default function AdminPanel() {
|
|
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
|
const [username, setUsername] = useState('admin');
|
|
const [password, setPassword] = useState('');
|
|
const [links, setLinks] = useState([]);
|
|
const [analytics, setAnalytics] = useState([]);
|
|
const [form, setForm] = useState(initialForm);
|
|
const [error, setError] = useState('');
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
async function loadAdminData() {
|
|
const [linksResponse, analyticsResponse] = await Promise.all([
|
|
fetch('/api/links'),
|
|
fetch('/api/analytics')
|
|
]);
|
|
|
|
if (!linksResponse.ok || !analyticsResponse.ok) {
|
|
setError('Impossible de charger les données admin.');
|
|
return;
|
|
}
|
|
|
|
const linksData = await linksResponse.json();
|
|
const analyticsData = await analyticsResponse.json();
|
|
|
|
setLinks(linksData.links || []);
|
|
setAnalytics(analyticsData.analytics || []);
|
|
setError('');
|
|
}
|
|
|
|
useEffect(() => {
|
|
async function checkSession() {
|
|
const response = await fetch('/api/admin/session');
|
|
const data = await response.json();
|
|
|
|
if (data.authenticated) {
|
|
setIsAuthenticated(true);
|
|
await loadAdminData();
|
|
}
|
|
|
|
setLoading(false);
|
|
}
|
|
|
|
checkSession();
|
|
}, []);
|
|
|
|
async function handleLogin(event) {
|
|
event.preventDefault();
|
|
setError('');
|
|
|
|
const response = await fetch('/api/admin/login', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ username, password })
|
|
});
|
|
|
|
if (!response.ok) {
|
|
setError('Identifiant ou mot de passe invalide.');
|
|
return;
|
|
}
|
|
|
|
setIsAuthenticated(true);
|
|
setPassword('');
|
|
await loadAdminData();
|
|
}
|
|
|
|
async function handleLogout() {
|
|
await fetch('/api/admin/logout', { method: 'POST' });
|
|
setIsAuthenticated(false);
|
|
setLinks([]);
|
|
setAnalytics([]);
|
|
}
|
|
|
|
async function handleAddLink(event) {
|
|
event.preventDefault();
|
|
setError('');
|
|
|
|
const response = await fetch('/api/links', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(form)
|
|
});
|
|
|
|
if (!response.ok) {
|
|
setError("Impossible d'ajouter le lien.");
|
|
return;
|
|
}
|
|
|
|
setForm(initialForm);
|
|
await loadAdminData();
|
|
}
|
|
|
|
async function handleDeleteLink(linkId) {
|
|
setError('');
|
|
const response = await fetch(`/api/links/${linkId}`, { method: 'DELETE' });
|
|
|
|
if (!response.ok) {
|
|
setError('Impossible de supprimer ce lien.');
|
|
return;
|
|
}
|
|
|
|
setError('');
|
|
await loadAdminData();
|
|
}
|
|
|
|
const totalClicks = useMemo(
|
|
() => analytics.reduce((sum, item) => sum + (item.clicks || 0), 0),
|
|
[analytics]
|
|
);
|
|
|
|
if (loading) {
|
|
return (
|
|
<main className="page">
|
|
<div className="container admin-container">
|
|
<p className="empty">Chargement...</p>
|
|
</div>
|
|
</main>
|
|
);
|
|
}
|
|
|
|
if (!isAuthenticated) {
|
|
return (
|
|
<main className="page">
|
|
<div className="container admin-container">
|
|
<h1 className="admin-title">Admin</h1>
|
|
<p className="bio">Accès protégé aux analytics et à la gestion des liens.</p>
|
|
|
|
<form className="admin-form" onSubmit={handleLogin}>
|
|
<input
|
|
className="search"
|
|
name="username"
|
|
autoComplete="username"
|
|
value={username}
|
|
onChange={(event) => setUsername(event.target.value)}
|
|
placeholder="Identifiant"
|
|
required
|
|
/>
|
|
<input
|
|
className="search"
|
|
name="password"
|
|
autoComplete="current-password"
|
|
type="password"
|
|
value={password}
|
|
onChange={(event) => setPassword(event.target.value)}
|
|
placeholder="Mot de passe"
|
|
required
|
|
/>
|
|
<button className="theme-toggle" type="submit">
|
|
Se connecter
|
|
</button>
|
|
</form>
|
|
|
|
{error ? <p className="admin-error">{error}</p> : null}
|
|
</div>
|
|
</main>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<main className="page">
|
|
<div className="container admin-container">
|
|
<div className="admin-head">
|
|
<h1 className="admin-title">Menu Admin</h1>
|
|
<button className="theme-toggle" type="button" onClick={handleLogout}>
|
|
Déconnexion
|
|
</button>
|
|
</div>
|
|
|
|
<section className="admin-section">
|
|
<h2 className="admin-subtitle">Analytics</h2>
|
|
<p className="bio">Total clics: {totalClicks}</p>
|
|
<div className="admin-list">
|
|
{analytics.map((item) => (
|
|
<div key={item.id} className="admin-row">
|
|
<div>
|
|
<strong>{item.name}</strong>
|
|
<div className="link-description">{item.url}</div>
|
|
</div>
|
|
<div className="admin-count">{item.clicks} clics</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</section>
|
|
|
|
<section className="admin-section">
|
|
<h2 className="admin-subtitle">Ajouter un lien</h2>
|
|
<form className="admin-form" onSubmit={handleAddLink}>
|
|
<input
|
|
className="search"
|
|
name="name"
|
|
value={form.name}
|
|
onChange={(event) => setForm({ ...form, name: event.target.value })}
|
|
placeholder="Nom"
|
|
required
|
|
/>
|
|
<input
|
|
className="search"
|
|
name="url"
|
|
value={form.url}
|
|
onChange={(event) => setForm({ ...form, url: event.target.value })}
|
|
placeholder="URL"
|
|
required
|
|
/>
|
|
<input
|
|
className="search"
|
|
name="icon"
|
|
value={form.icon}
|
|
onChange={(event) => setForm({ ...form, icon: event.target.value })}
|
|
placeholder="Icône (ex: /github.png)"
|
|
/>
|
|
<input
|
|
className="search"
|
|
name="description"
|
|
value={form.description}
|
|
onChange={(event) => setForm({ ...form, description: event.target.value })}
|
|
placeholder="Description"
|
|
/>
|
|
<input
|
|
className="search"
|
|
name="category"
|
|
value={form.category}
|
|
onChange={(event) => setForm({ ...form, category: event.target.value })}
|
|
placeholder="Catégorie"
|
|
/>
|
|
<button className="theme-toggle" type="submit">
|
|
Ajouter
|
|
</button>
|
|
</form>
|
|
</section>
|
|
|
|
<section className="admin-section">
|
|
<h2 className="admin-subtitle">Supprimer un lien</h2>
|
|
<div className="admin-list">
|
|
{links.map((link) => (
|
|
<div key={link.id} className="admin-row">
|
|
<div>
|
|
<strong>{link.name}</strong>
|
|
<div className="link-description">{link.url}</div>
|
|
</div>
|
|
<button
|
|
className="danger-btn"
|
|
type="button"
|
|
onClick={() => handleDeleteLink(link.id)}
|
|
>
|
|
Supprimer
|
|
</button>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</section>
|
|
|
|
{error ? <p className="admin-error">{error}</p> : null}
|
|
</div>
|
|
</main>
|
|
);
|
|
}
|