refonte css of site

This commit is contained in:
Arthur Puechberty
2026-01-17 20:59:52 +01:00
parent 90339f9323
commit 95cd796839
20 changed files with 2951 additions and 1109 deletions
+11 -3
View File
@@ -1,4 +1,4 @@
const { Events } = require("discord.js");
const { Events, EmbedBuilder } = require("discord.js");
const db = require("../db");
module.exports = {
@@ -10,7 +10,7 @@ module.exports = {
(err, row) => {
if (err || !row || !row.enabled) return;
let msg = row.message;
let msg = row.message || "Bienvenue {mention} sur {server} !";
msg = msg
.replace("{user}", member.user.username)
@@ -19,7 +19,15 @@ module.exports = {
const channel = member.guild.channels.cache.get(row.channel_id);
if (channel) {
channel.send(msg);
const embed = new EmbedBuilder()
.setColor(0x57F287)
.setTitle("👋 Bienvenue !")
.setDescription(msg)
.setThumbnail(member.user.displayAvatarURL({ dynamic: true, size: 256 }))
.setFooter({ text: member.guild.name, iconURL: member.guild.iconURL({ dynamic: true }) })
.setTimestamp();
channel.send({ embeds: [embed] });
}
}
);
+11 -3
View File
@@ -1,4 +1,4 @@
const { Events } = require("discord.js");
const { Events, EmbedBuilder } = require("discord.js");
const db = require("../db");
module.exports = {
@@ -10,7 +10,7 @@ module.exports = {
(err, row) => {
if (err || !row || !row.enabled) return;
let msg = row.message;
let msg = row.message || "Au revoir {user}, tu vas nous manquer !";
msg = msg
.replace("{user}", member.user.username)
@@ -18,7 +18,15 @@ module.exports = {
const channel = member.guild.channels.cache.get(row.channel_id);
if (channel) {
channel.send(msg);
const embed = new EmbedBuilder()
.setColor(0xED4245)
.setTitle("👋 Au revoir...")
.setDescription(msg)
.setThumbnail(member.user.displayAvatarURL({ dynamic: true, size: 256 }))
.setFooter({ text: member.guild.name, iconURL: member.guild.iconURL({ dynamic: true }) })
.setTimestamp();
channel.send({ embeds: [embed] });
}
}
);
+40 -15
View File
@@ -1,4 +1,4 @@
const { Events } = require("discord.js");
const { Events, EmbedBuilder } = require("discord.js");
const db = require("../db");
module.exports = {
@@ -31,25 +31,31 @@ module.exports = {
[guildId],
(err, row) => {
if (err || !row || !row.enabled || !row.gain_xp_on_message) return;
if (row.role_with_without_type === "with") {
const roleWithWithoutType = row.role_with_without_type || "without";
const salonWithWithoutType = row.salon_with_without_type || "without";
if (roleWithWithoutType === "with") {
const userRoles = message.member.roles.cache;
const requiredRoles = JSON.parse(row.role_with_without_xp || "[]");
if (!requiredRoles.some(roleId => userRoles.has(roleId))) {
return;
}
} else if (row.role_with_without_type === "without") {
} else if (roleWithWithoutType === "without") {
const userRoles = message.member.roles.cache;
const excludedRoles = JSON.parse(row.role_with_without_xp || "[]");
if (excludedRoles.some(roleId => userRoles.has(roleId))) {
return;
}
} else if (row.salon_with_without_type === "with") {
}
if (salonWithWithoutType === "with") {
const channelId = message.channel.id;
const requiredChannels = JSON.parse(row.salon_with_without_xp || "[]");
if (!requiredChannels.includes(channelId)) {
return;
}
} else if (row.salon_with_without_type === "without") {
} else if (salonWithWithoutType === "without") {
const channelId = message.channel.id;
const excludedChannels = JSON.parse(row.salon_with_without_xp || "[]");
if (excludedChannels.includes(channelId)) {
@@ -64,13 +70,14 @@ module.exports = {
(err, userRow) => {
if (err) return;
const cooldownSeconds = row.cooldown_xp_message_seconds ?? 60;
const lastTimestamp = userRow ? userRow.last_xp_message_timestamp || 0 : 0;
if (now - lastTimestamp < row.cooldown_xp_message_seconds * 1000) {
if (now - lastTimestamp < cooldownSeconds * 1000) {
return;
}
const minXp = row.gain_xp_message_lower_bound;
const maxXp = row.gain_xp_message_upper_bound;
const minXp = row.gain_xp_message_lower_bound ?? 15;
const maxXp = row.gain_xp_message_upper_bound ?? 25;
const xpToAdd = Math.floor(Math.random() * (maxXp - minXp + 1)) + minXp;
let newXp;
@@ -84,17 +91,21 @@ module.exports = {
newLevel = 1;
}
const multiplier = row.multiplier_courbe_for_level;
const multiplier = row.multiplier_courbe_for_level ?? 100;
const courbeType = row.xp_courbe_type || "linear";
let fonction_courbe;
if (row.xp_courbe_type === "constante") {
if (courbeType === "constante") {
fonction_courbe = (level) => multiplier;
} else if (row.xp_courbe_type === "linear") {
} else if (courbeType === "linear") {
fonction_courbe = (level) => (level) * multiplier;
} else if (row.xp_courbe_type === "quadratic") {
} else if (courbeType === "quadratic") {
fonction_courbe = (level) => (level) * (level) * multiplier;
} else if (row.xp_courbe_type === "exponential") {
} else if (courbeType === "exponential") {
fonction_courbe = (level) => Math.pow(2, (level - 1)) * multiplier;
} else {
// Fallback au cas où
fonction_courbe = (level) => (level) * multiplier;
}
let xpForNextLevel = fonction_courbe(newLevel);
@@ -106,13 +117,27 @@ module.exports = {
if (row.level_announcements_enabled && (newLevel % row.level_annoncement_every_level === 0)) {
const channel = message.guild.channels.cache.get(row.level_announcements_channel_id);
if (channel) {
let announcementMsg = row.level_announcements_message;
let announcementMsg = row.level_announcements_message || "🎉 {mention} a atteint le niveau {level} !";
announcementMsg = announcementMsg
.replace("{user}", message.author.username)
.replace("{mention}", `<@${message.author.id}>`)
.replace("{level}", newLevel)
.replace("{level-xp}", xpForNextLevel);
channel.send(announcementMsg);
const embed = new EmbedBuilder()
.setColor(0xFEE75C)
.setTitle("🎉 Level Up !")
.setDescription(announcementMsg)
.setThumbnail(message.author.displayAvatarURL({ dynamic: true, size: 256 }))
.addFields(
{ name: "Nouveau niveau", value: `${newLevel}`, inline: true },
{ name: "XP pour le prochain", value: `${xpForNextLevel}`, inline: true }
)
.setFooter({ text: message.guild.name, iconURL: message.guild.iconURL({ dynamic: true }) })
.setTimestamp();
// @ mention car c'est une notification importante
channel.send({ content: `<@${message.author.id}>`, embeds: [embed] });
}
}
}
+259 -116
View File
@@ -1,137 +1,280 @@
/* ===== Reset minimal ===== */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
@import url('global.css');
/* ===== Navigation ===== */
.navbar {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 1000;
background-color: rgba(22, 27, 34, 0.95);
backdrop-filter: blur(10px);
border-bottom: 1px solid var(--border-color);
padding: var(--spacing-md) 0;
}
.navbar-container {
display: flex;
align-items: center;
justify-content: space-between;
max-width: 1200px;
margin: 0 auto;
padding: 0 var(--spacing-md);
}
.navbar-brand {
display: flex;
align-items: center;
gap: var(--spacing-sm);
font-size: 1.25rem;
font-weight: 700;
color: var(--text-primary);
text-decoration: none;
}
.navbar-brand img {
width: 32px;
height: 32px;
border-radius: 50%;
}
.navbar-nav {
display: flex;
align-items: center;
gap: var(--spacing-lg);
}
.navbar-link {
color: var(--text-secondary);
font-weight: 500;
text-decoration: none;
transition: color var(--transition-fast);
}
.navbar-link:hover {
color: var(--text-primary);
}
.navbar-user {
display: flex;
align-items: center;
gap: var(--spacing-sm);
}
.navbar-user img {
width: 32px;
height: 32px;
border-radius: 50%;
border: 2px solid var(--primary);
}
.navbar-user span {
color: var(--text-primary);
font-weight: 500;
}
.navbar-actions {
display: flex;
align-items: center;
gap: var(--spacing-sm);
}
/* ===== Dashboard Layout ===== */
body {
background-color: #1e1f29; /* fond sombre type Discord */
color: #ffffff;
padding-top: 80px;
min-height: 100vh;
}
.dashboard-container {
max-width: 1200px;
margin: 0 auto;
padding: var(--spacing-xl) var(--spacing-md);
}
/* ===== Dashboard Header ===== */
.dashboard-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: var(--spacing-xl);
flex-wrap: wrap;
gap: var(--spacing-md);
}
.user-info {
display: flex;
align-items: center;
gap: var(--spacing-md);
}
.user-avatar {
width: 64px;
height: 64px;
border-radius: 50%;
border: 3px solid var(--primary);
box-shadow: 0 0 20px rgba(88, 101, 242, 0.3);
}
.user-details h1 {
font-size: 1.5rem;
margin-bottom: var(--spacing-xs);
color: var(--text-primary);
}
.user-details p {
color: var(--text-secondary);
font-size: 0.95rem;
}
/* ===== Section Title ===== */
.section-title {
font-size: 1.25rem;
color: var(--text-primary);
margin-bottom: var(--spacing-lg);
padding-bottom: var(--spacing-sm);
border-bottom: 1px solid var(--border-color);
}
/* ===== Guilds Grid ===== */
.guilds-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: var(--spacing-lg);
}
.guild-card {
background-color: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: var(--border-radius-lg);
overflow: hidden;
transition: transform var(--transition-normal), border-color var(--transition-normal), box-shadow var(--transition-normal);
cursor: pointer;
}
.guild-card:hover {
transform: translateY(-4px);
border-color: var(--primary);
box-shadow: 0 8px 30px rgba(88, 101, 242, 0.15);
}
.guild-card-header {
height: 80px;
background: linear-gradient(135deg, var(--primary), #7289da);
position: relative;
overflow: visible;
}
.guild-card-avatar {
position: absolute;
bottom: -30px;
left: var(--spacing-md);
width: 60px;
height: 60px;
border-radius: 50%;
border: 4px solid var(--bg-card);
background-color: var(--bg-dark);
object-fit: cover;
}
.guild-card-body {
padding: var(--spacing-lg);
padding-top: calc(var(--spacing-lg) + 20px);
}
.guild-card-name {
font-size: 1.1rem;
font-weight: 600;
color: var(--text-primary);
margin-bottom: var(--spacing-xs);
}
.guild-card-info {
color: var(--text-secondary);
font-size: 0.85rem;
}
.guild-card-footer {
padding: var(--spacing-md);
border-top: 1px solid var(--border-color);
display: flex;
justify-content: flex-end;
}
/* ===== Empty State ===== */
.empty-state {
text-align: center;
padding: var(--spacing-2xl);
background-color: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: var(--border-radius-lg);
}
.empty-state-icon {
font-size: 48px;
margin-bottom: var(--spacing-md);
}
.empty-state h3 {
font-size: 1.25rem;
color: var(--text-primary);
margin-bottom: var(--spacing-sm);
}
.empty-state p {
color: var(--text-secondary);
margin-bottom: var(--spacing-lg);
}
/* ===== Loading State ===== */
.loading {
display: flex;
flex-direction: column;
align-items: center;
padding: 20px;
justify-content: center;
padding: var(--spacing-2xl);
}
/* ===== Navigation ===== */
nav {
width: 100%;
max-width: 800px;
display: flex;
justify-content: flex-start;
background-color: #2f3136;
padding: 10px 20px;
border-radius: 10px;
margin-bottom: 30px;
}
nav a {
color: #ffffff;
text-decoration: none;
font-weight: 500;
transition: color 0.2s;
}
nav a:hover {
color: #5865f2;
}
/* ===== Titres ===== */
h1, h2 {
text-align: center;
}
h1 {
font-size: 2.5rem;
margin-bottom: 15px;
}
h2 {
font-size: 1.8rem;
margin: 25px 0 10px 0;
border-bottom: 2px solid #5865f2;
display: inline-block;
padding-bottom: 5px;
}
/* ===== Avatar ===== */
#avatar {
width: 80px;
height: 80px;
border-radius: 50%;
margin: 10px 0 20px 0;
border: 2px solid #5865f2;
}
/* ===== Bouton d'invitation ===== */
#invite-link {
display: inline-block;
background-color: #5865f2;
color: white;
padding: 10px 20px;
border-radius: 8px;
text-decoration: none;
font-weight: 600;
margin-bottom: 30px;
transition: background 0.2s;
}
#invite-link:hover {
background-color: #4752c4;
}
/* ===== Liste des guilds ===== */
ul#guilds-list {
list-style: none;
width: 100%;
max-width: 800px;
padding: 0;
}
ul#guilds-list li {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 15px;
margin-bottom: 10px;
border-radius: 10px;
background-color: #161a22;
cursor: pointer;
transition: background 0.2s, color 0.2s;
}
ul#guilds-list li:hover {
background-color: #5865f2;
color: white;
}
ul#guilds-list li img {
.spinner {
width: 40px;
height: 40px;
border: 3px solid var(--border-color);
border-top-color: var(--primary);
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.loading p {
margin-top: var(--spacing-md);
color: var(--text-secondary);
}
/* ===== Responsive ===== */
@media (max-width: 600px) {
body {
padding: 10px;
}
nav {
@media (max-width: 768px) {
.dashboard-header {
flex-direction: column;
align-items: center;
gap: 10px;
align-items: flex-start;
}
ul#guilds-list li {
flex-direction: column;
align-items: center;
text-align: center;
.user-avatar {
width: 48px;
height: 48px;
}
ul#guilds-list li img {
margin-bottom: 5px;
.user-details h1 {
font-size: 1.25rem;
}
.guilds-grid {
grid-template-columns: 1fr;
}
}
@media (max-width: 480px) {
.navbar-nav .navbar-link {
display: none;
}
}
+121 -74
View File
@@ -1,108 +1,155 @@
<!DOCTYPE html>
<html>
<html lang="fr">
<head>
<title>Tableau de bord</title>
<title>Dashboard - LazyBot</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/dashboard.css">
</head>
<body>
<nav>
<a href="/">Accueil</a>
<!-- Navigation -->
<nav class="navbar">
<div class="navbar-container">
<a href="/" class="navbar-brand">
<img id="bot-avatar" src="https://cdn.discordapp.com/embed/avatars/0.png" alt="LazyBot">
<span>LazyBot</span>
</a>
<div class="navbar-nav">
<a href="/" class="navbar-link">Accueil</a>
<a href="/dashboard" class="navbar-link">Dashboard</a>
<div class="navbar-actions">
<div class="navbar-user">
<img id="nav-avatar" src="" alt="Avatar">
<span id="nav-username"></span>
</div>
<a href="/auth/logout" class="btn btn-secondary btn-sm">Déconnexion</a>
</div>
</div>
</div>
</nav>
<!-- Dashboard Content -->
<div class="dashboard-container">
<!-- Dashboard Header -->
<div class="dashboard-header">
<div class="user-info">
<img id="user-avatar" class="user-avatar" src="" alt="Avatar">
<div class="user-details">
<h1 id="greeting">Chargement...</h1>
<img id="avatar" src="" alt="Avatar">
<p>Sélectionnez un serveur pour le configurer</p>
</div>
</div>
<a id="invite-link" href="#" class="btn btn-primary">
Ajouter le bot à un serveur
</a>
</div>
<a id="invite-link" href="#">Ajouter le bot à votre serveur</a>
<!-- Guilds Section -->
<h2 class="section-title">🏠 Mes serveurs</h2>
<h2>Mes serveurs qui ont le bot :</h2>
<ul id="guilds-list"></ul> <!-- Ici on va lister les guilds -->
<div id="guilds-container">
<div class="loading">
<div class="spinner"></div>
<p>Chargement des serveurs...</p>
</div>
</div>
</div>
<script>
// --- Affichage des infos utilisateur ---
fetch("/api/user")
// Charger les infos du bot
fetch("/api/bot-info")
.then(res => res.json())
.then(bot => {
document.getElementById("bot-avatar").src = `https://cdn.discordapp.com/avatars/${bot.id}/${bot.avatar}.png`;
})
.catch(() => {});
// Charger les infos utilisateur
fetch("/api/user")
.then(res => {
if (!res.ok) throw new Error("Non connecté");
return res.json();
})
.then(user => {
document.getElementById("greeting").textContent = `Salut ${user.username}#${user.discriminator} !`;
document.getElementById("avatar").src = `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.png`;
const avatarUrl = `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.png`;
document.getElementById("greeting").textContent = `Bienvenue, ${user.username} !`;
document.getElementById("user-avatar").src = avatarUrl;
document.getElementById("nav-avatar").src = avatarUrl;
document.getElementById("nav-username").textContent = user.username;
})
.catch(() => {
document.getElementById("greeting").textContent = "Utilisateur non connecté.";
window.location.href = "/auth/login";
});
// --- Affichage des guilds de l'utilisateur ---
// Charger les guilds
fetch("/api/guilds")
.then(res => res.json())
.then(guilds => {
const list = document.getElementById("guilds-list");
const container = document.getElementById("guilds-container");
if (guilds.length === 0) {
list.innerHTML = "<li>Aucun serveur disponible</li>";
} else {
guilds.forEach(g => {
const li = document.createElement("li");
li.style.cursor = "pointer";
li.style.padding = "8px";
li.style.border = "1px solid #ccc";
li.style.margin = "5px 0";
li.style.display = "flex";
li.style.alignItems = "center";
li.style.gap = "10px";
li.style.borderRadius = "8px";
li.style.backgroundColor = "#161a22"; // fond sombre pour chaque item
li.style.transition = "background 0.2s";
li.addEventListener("click", () => {
window.location.href = `/guild/${g.id}`;
});
li.addEventListener("mouseover", () => {
li.style.backgroundColor = "#5865F2"; // couleur Discord au hover
li.style.color = "white";
});
li.addEventListener("mouseout", () => {
li.style.backgroundColor = "#161a22";
li.style.color = "";
});
// Logo de la guild
if (g.icon) {
const img = document.createElement("img");
img.src = `https://cdn.discordapp.com/icons/${g.id}/${g.icon}.png`;
img.alt = `${g.name} logo`;
img.style.width = "40px";
img.style.height = "40px";
img.style.borderRadius = "50%";
li.appendChild(img);
}
// Nom de la guild
const span = document.createElement("span");
span.textContent = g.name;
li.appendChild(span);
list.appendChild(li);
});
}
})
.catch(() => {
document.getElementById("guilds-list").innerHTML = "<li>Impossible de récupérer les guilds.</li>";
});
container.innerHTML = `
<div class="empty-state">
<div class="empty-state-icon">🤖</div>
<h3>Aucun serveur trouvé</h3>
<p>Ajoutez LazyBot à un serveur pour commencer à le configurer.</p>
<a id="invite-empty" href="#" class="btn btn-primary">Ajouter à un serveur</a>
</div>
`;
// Récupérer le lien pour le bouton empty state
fetch("/invite-bot")
.then(res => res.json())
.then(data => {
const link = document.getElementById("invite-link");
link.href = data.url; // met le lien dynamique
const btn = document.getElementById("invite-empty");
if (btn) btn.href = data.url;
});
} else {
container.innerHTML = '<div class="guilds-grid"></div>';
const grid = container.querySelector('.guilds-grid');
guilds.forEach(g => {
const card = document.createElement("div");
card.className = "guild-card";
card.onclick = () => window.location.href = `/guild/${g.id}`;
const iconUrl = g.icon
? `https://cdn.discordapp.com/icons/${g.id}/${g.icon}.png`
: `https://cdn.discordapp.com/embed/avatars/0.png`;
card.innerHTML = `
<div class="guild-card-header"></div>
<img class="guild-card-avatar" src="${iconUrl}" alt="${g.name}">
<div class="guild-card-body">
<h3 class="guild-card-name">${g.name}</h3>
<p class="guild-card-info">Cliquez pour configurer</p>
</div>
<div class="guild-card-footer">
<span class="btn btn-sm btn-primary">Configurer →</span>
</div>
`;
grid.appendChild(card);
});
}
})
.catch(() => {
console.log("Impossible de récupérer le lien du bot.");
document.getElementById("guilds-container").innerHTML = `
<div class="empty-state">
<div class="empty-state-icon">❌</div>
<h3>Erreur de chargement</h3>
<p>Impossible de récupérer la liste des serveurs.</p>
<button class="btn btn-primary" onclick="location.reload()">Réessayer</button>
</div>
`;
});
// Lien d'invitation
fetch("/invite-bot")
.then(res => res.json())
.then(data => {
document.getElementById("invite-link").href = data.url;
})
.catch(() => console.log("Impossible de récupérer le lien du bot."));
</script>
</body>
</html>
+406
View File
@@ -0,0 +1,406 @@
/* ===== Variables CSS ===== */
:root {
/* Couleurs principales */
--primary: #5865f2;
--primary-hover: #4752c4;
--primary-light: rgba(88, 101, 242, 0.1);
/* Couleurs de fond */
--bg-dark: #0d1117;
--bg-card: #161b22;
--bg-card-hover: #1c2128;
--bg-input: #0d1117;
--bg-nav: #161b22;
/* Couleurs de texte */
--text-primary: #e6edf3;
--text-secondary: #8b949e;
--text-muted: #6e7681;
/* Couleurs d'état */
--success: #238636;
--success-light: rgba(35, 134, 54, 0.15);
--danger: #da3633;
--danger-light: rgba(218, 54, 51, 0.15);
--warning: #d29922;
--warning-light: rgba(210, 153, 34, 0.15);
/* Bordures */
--border-color: #30363d;
--border-radius: 6px;
--border-radius-lg: 12px;
/* Ombres */
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.4);
--shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.5);
/* Transitions */
--transition-fast: 150ms ease;
--transition-normal: 250ms ease;
/* Espacements */
--spacing-xs: 4px;
--spacing-sm: 8px;
--spacing-md: 16px;
--spacing-lg: 24px;
--spacing-xl: 32px;
--spacing-2xl: 48px;
}
/* ===== Reset ===== */
*,
*::before,
*::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html {
scroll-behavior: smooth;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif;
font-size: 14px;
line-height: 1.5;
color: var(--text-primary);
background-color: var(--bg-dark);
min-height: 100vh;
}
a {
color: var(--primary);
text-decoration: none;
transition: color var(--transition-fast);
}
a:hover {
color: var(--primary-hover);
text-decoration: none;
}
img {
max-width: 100%;
height: auto;
}
/* ===== Typography ===== */
h1, h2, h3, h4, h5, h6 {
font-weight: 600;
line-height: 1.25;
color: var(--text-primary);
}
h1 { font-size: 2rem; }
h2 { font-size: 1.5rem; }
h3 { font-size: 1.25rem; }
h4 { font-size: 1rem; }
p {
color: var(--text-secondary);
}
/* ===== Buttons ===== */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--spacing-sm);
padding: var(--spacing-sm) var(--spacing-md);
font-size: 14px;
font-weight: 500;
line-height: 20px;
border-radius: var(--border-radius);
border: 1px solid transparent;
cursor: pointer;
transition: all var(--transition-fast);
text-decoration: none;
}
.btn:hover {
text-decoration: none;
}
.btn-primary {
background-color: var(--primary);
color: white;
border-color: var(--primary);
}
.btn-primary:hover {
background-color: var(--primary-hover);
border-color: var(--primary-hover);
color: white;
}
.btn-secondary {
background-color: var(--bg-card);
color: var(--text-primary);
border-color: var(--border-color);
}
.btn-secondary:hover {
background-color: var(--bg-card-hover);
border-color: var(--text-muted);
}
.btn-success {
background-color: var(--success);
color: white;
border-color: var(--success);
}
.btn-success:hover {
background-color: #2ea043;
border-color: #2ea043;
color: white;
}
.btn-danger {
background-color: var(--danger);
color: white;
border-color: var(--danger);
}
.btn-danger:hover {
background-color: #f85149;
border-color: #f85149;
color: white;
}
.btn-lg {
padding: var(--spacing-md) var(--spacing-lg);
font-size: 16px;
}
.btn-sm {
padding: var(--spacing-xs) var(--spacing-sm);
font-size: 12px;
}
/* ===== Form Elements ===== */
.form-group {
margin-bottom: var(--spacing-md);
}
.form-label {
display: block;
margin-bottom: var(--spacing-sm);
font-weight: 500;
color: var(--text-primary);
}
.form-input,
.form-select,
.form-textarea {
width: 100%;
padding: var(--spacing-sm) var(--spacing-md);
font-size: 14px;
line-height: 20px;
color: var(--text-primary);
background-color: var(--bg-input);
border: 1px solid var(--border-color);
border-radius: var(--border-radius);
transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}
.form-input:focus,
.form-select:focus,
.form-textarea:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 3px var(--primary-light);
}
.form-input::placeholder {
color: var(--text-muted);
}
.form-textarea {
resize: vertical;
min-height: 100px;
}
.form-select {
cursor: pointer;
appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='%238b949e' viewBox='0 0 16 16'%3E%3Cpath d='M8 11L3 6h10l-5 5z'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 12px center;
padding-right: 36px;
}
.form-select[multiple] {
background-image: none;
padding-right: var(--spacing-md);
height: auto;
min-height: 120px;
}
.form-select option {
padding: var(--spacing-sm);
}
.form-checkbox {
display: flex;
align-items: center;
gap: var(--spacing-sm);
cursor: pointer;
}
.form-checkbox input[type="checkbox"] {
width: 16px;
height: 16px;
accent-color: var(--primary);
cursor: pointer;
}
.form-hint {
margin-top: var(--spacing-xs);
font-size: 12px;
color: var(--text-muted);
}
/* ===== Cards ===== */
.card {
background-color: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: var(--border-radius-lg);
overflow: hidden;
}
.card-header {
padding: var(--spacing-md) var(--spacing-lg);
border-bottom: 1px solid var(--border-color);
background-color: var(--bg-card-hover);
}
.card-header h2,
.card-header h3 {
margin: 0;
display: flex;
align-items: center;
gap: var(--spacing-sm);
}
.card-body {
padding: var(--spacing-lg);
}
.card-footer {
padding: var(--spacing-md) var(--spacing-lg);
border-top: 1px solid var(--border-color);
background-color: var(--bg-card-hover);
}
/* ===== Alerts ===== */
.alert {
padding: var(--spacing-md);
border-radius: var(--border-radius);
margin-bottom: var(--spacing-md);
display: flex;
align-items: flex-start;
gap: var(--spacing-sm);
}
.alert-success {
background-color: var(--success-light);
border: 1px solid var(--success);
color: #3fb950;
}
.alert-danger {
background-color: var(--danger-light);
border: 1px solid var(--danger);
color: #f85149;
}
.alert-warning {
background-color: var(--warning-light);
border: 1px solid var(--warning);
color: #d29922;
}
/* ===== Badge ===== */
.badge {
display: inline-flex;
align-items: center;
padding: 2px 8px;
font-size: 12px;
font-weight: 500;
border-radius: 20px;
}
.badge-primary {
background-color: var(--primary-light);
color: var(--primary);
}
.badge-success {
background-color: var(--success-light);
color: #3fb950;
}
.badge-danger {
background-color: var(--danger-light);
color: #f85149;
}
/* ===== Container ===== */
.container {
width: 100%;
max-width: 1200px;
margin: 0 auto;
padding: 0 var(--spacing-md);
}
.container-sm {
max-width: 800px;
}
.container-lg {
max-width: 1400px;
}
/* ===== Grid ===== */
.grid {
display: grid;
gap: var(--spacing-lg);
}
.grid-2 {
grid-template-columns: repeat(2, 1fr);
}
.grid-3 {
grid-template-columns: repeat(3, 1fr);
}
@media (max-width: 768px) {
.grid-2,
.grid-3 {
grid-template-columns: 1fr;
}
}
/* ===== Utilities ===== */
.text-center { text-align: center; }
.text-right { text-align: right; }
.text-muted { color: var(--text-muted); }
.text-success { color: #3fb950; }
.text-danger { color: #f85149; }
.mt-sm { margin-top: var(--spacing-sm); }
.mt-md { margin-top: var(--spacing-md); }
.mt-lg { margin-top: var(--spacing-lg); }
.mb-sm { margin-bottom: var(--spacing-sm); }
.mb-md { margin-bottom: var(--spacing-md); }
.mb-lg { margin-bottom: var(--spacing-lg); }
.flex { display: flex; }
.flex-center { align-items: center; justify-content: center; }
.flex-between { justify-content: space-between; }
.gap-sm { gap: var(--spacing-sm); }
.gap-md { gap: var(--spacing-md); }
+533 -103
View File
@@ -1,143 +1,573 @@
/* ===== Reset minimal ===== */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
@import url('global.css');
/* ===== Layout ===== */
body {
display: flex;
min-height: 100vh;
overflow-x: hidden;
}
body {
background-color: #1e1f29; /* fond sombre type Discord */
color: #ffffff;
/* ===== Sidebar ===== */
.sidebar {
width: 260px;
background-color: var(--bg-card);
border-right: 1px solid var(--border-color);
position: fixed;
top: 0;
left: 0;
bottom: 0;
display: flex;
flex-direction: column;
align-items: center;
padding: 20px;
z-index: 100;
}
/* ===== Navigation ===== */
nav {
width: 100%;
max-width: 900px;
.sidebar-header {
padding: var(--spacing-lg);
border-bottom: 1px solid var(--border-color);
}
.sidebar-brand {
display: flex;
justify-content: flex-start;
background-color: #2f3136;
padding: 10px 20px;
border-radius: 10px;
margin-bottom: 30px;
}
nav a {
color: #ffffff;
align-items: center;
gap: var(--spacing-sm);
text-decoration: none;
font-weight: 500;
margin-right: 15px;
transition: color 0.2s;
color: var(--text-primary);
font-weight: 700;
font-size: 1.1rem;
margin-bottom: var(--spacing-md);
}
nav a:hover {
color: #5865f2;
.sidebar-brand img {
width: 32px;
height: 32px;
border-radius: 50%;
}
/* ===== Titres ===== */
h1 {
text-align: center;
font-size: 2.5rem;
margin-bottom: 20px;
.guild-info {
display: flex;
align-items: center;
gap: var(--spacing-sm);
padding: var(--spacing-sm);
background-color: var(--bg-dark);
border-radius: var(--border-radius);
}
h2 {
font-size: 1.8rem;
margin: 25px 0 10px 0;
border-bottom: 2px solid #5865f2;
display: inline-block;
padding-bottom: 5px;
text-align: center;
.guild-info img {
width: 40px;
height: 40px;
border-radius: 50%;
}
/* ===== Formulaires ===== */
form {
background-color: #2f3136;
padding: 20px;
border-radius: 12px;
margin-bottom: 30px;
width: 100%;
max-width: 700px;
.guild-info-text {
flex: 1;
min-width: 0;
}
form label {
display: block;
margin-bottom: 15px;
font-weight: 500;
}
form input[type="checkbox"] {
margin-right: 10px;
}
form select,
form textarea {
width: 100%;
padding: 8px;
border-radius: 6px;
border: 1px solid #5865f2;
background-color: #161a22;
color: #ffffff;
margin-top: 5px;
font-size: 1rem;
}
form textarea {
resize: vertical;
}
form button {
background-color: #5865f2;
color: white;
padding: 10px 20px;
border: none;
border-radius: 8px;
.guild-info-name {
font-weight: 600;
color: var(--text-primary);
font-size: 0.9rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.guild-info-id {
color: var(--text-muted);
font-size: 0.75rem;
}
/* ===== Sidebar Navigation ===== */
.sidebar-nav {
flex: 1;
padding: var(--spacing-md);
overflow-y: auto;
}
.nav-section {
margin-bottom: var(--spacing-lg);
}
.nav-section-title {
font-size: 0.7rem;
font-weight: 700;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.05em;
padding: var(--spacing-xs) var(--spacing-sm);
margin-bottom: var(--spacing-xs);
}
.nav-item {
display: flex;
align-items: center;
gap: var(--spacing-sm);
padding: var(--spacing-sm) var(--spacing-md);
border-radius: var(--border-radius);
color: var(--text-secondary);
text-decoration: none;
font-size: 0.9rem;
font-weight: 500;
transition: all var(--transition-fast);
cursor: pointer;
transition: background 0.2s;
}
form button:hover {
background-color: #4752c4;
.nav-item:hover {
background-color: var(--bg-dark);
color: var(--text-primary);
}
form small {
.nav-item.active {
background-color: var(--primary-light);
color: var(--primary);
}
.nav-item-icon {
font-size: 1.1rem;
width: 24px;
text-align: center;
}
/* ===== Sidebar Footer ===== */
.sidebar-footer {
padding: var(--spacing-md);
border-top: 1px solid var(--border-color);
}
.sidebar-footer a {
display: flex;
align-items: center;
gap: var(--spacing-sm);
padding: var(--spacing-sm) var(--spacing-md);
border-radius: var(--border-radius);
color: var(--text-secondary);
text-decoration: none;
font-size: 0.9rem;
transition: all var(--transition-fast);
}
.sidebar-footer a:hover {
background-color: var(--bg-dark);
color: var(--text-primary);
}
/* ===== Main Content ===== */
.main-content {
flex: 1;
margin-left: 260px;
min-height: 100vh;
display: flex;
flex-direction: column;
}
/* ===== Top Header ===== */
.top-header {
background-color: var(--bg-card);
border-bottom: 1px solid var(--border-color);
padding: var(--spacing-md) var(--spacing-xl);
display: flex;
align-items: center;
justify-content: space-between;
position: sticky;
top: 0;
z-index: 50;
}
.breadcrumb {
display: flex;
align-items: center;
gap: var(--spacing-sm);
color: var(--text-secondary);
font-size: 0.9rem;
}
.breadcrumb a {
color: var(--text-secondary);
text-decoration: none;
}
.breadcrumb a:hover {
color: var(--text-primary);
}
.breadcrumb-separator {
color: var(--text-muted);
}
.breadcrumb-current {
color: var(--text-primary);
font-weight: 500;
}
.header-user {
display: flex;
align-items: center;
gap: var(--spacing-sm);
}
.header-user img {
width: 32px;
height: 32px;
border-radius: 50%;
}
.header-user span {
color: var(--text-primary);
font-weight: 500;
}
/* ===== Content Area ===== */
.content-area {
flex: 1;
padding: var(--spacing-xl);
max-width: 900px;
}
/* ===== Config Sections ===== */
.config-section {
display: none;
}
.config-section.active {
display: block;
margin-top: 10px;
font-size: 0.85rem;
color: #b9bbbe;
}
form small ul {
list-style: disc inside;
margin-top: 5px;
/* ===== Config Card ===== */
.config-card {
background-color: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: var(--border-radius-lg);
margin-bottom: var(--spacing-lg);
overflow: hidden;
}
#status-welcome-form,
#status-goodbye-form,
#status-autorole-form,
#status-autorole-vocal-form {
margin-top: 10px;
.config-card-header {
padding: var(--spacing-lg);
border-bottom: 1px solid var(--border-color);
display: flex;
align-items: center;
justify-content: space-between;
}
.config-card-title {
display: flex;
align-items: center;
gap: var(--spacing-sm);
}
.config-card-title h3 {
font-size: 1.1rem;
font-weight: 600;
color: var(--text-primary);
}
/* ===== Select multiple pour vocal ===== */
select[multiple] {
.config-card-title .icon {
font-size: 1.25rem;
}
.config-card-body {
padding: var(--spacing-lg);
}
.config-card-footer {
padding: var(--spacing-md) var(--spacing-lg);
border-top: 1px solid var(--border-color);
background-color: var(--bg-dark);
display: flex;
align-items: center;
justify-content: space-between;
}
/* ===== Toggle Switch ===== */
.toggle-switch {
position: relative;
width: 48px;
height: 24px;
}
.toggle-switch input {
opacity: 0;
width: 0;
height: 0;
}
.toggle-slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: var(--bg-dark);
border: 1px solid var(--border-color);
transition: var(--transition-fast);
border-radius: 24px;
}
.toggle-slider:before {
position: absolute;
content: "";
height: 18px;
width: 18px;
left: 2px;
bottom: 2px;
background-color: var(--text-muted);
transition: var(--transition-fast);
border-radius: 50%;
}
.toggle-switch input:checked + .toggle-slider {
background-color: var(--primary);
border-color: var(--primary);
}
.toggle-switch input:checked + .toggle-slider:before {
background-color: white;
transform: translateX(24px);
}
/* ===== Form Styles ===== */
.form-row {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: var(--spacing-md);
}
.form-group {
margin-bottom: var(--spacing-md);
}
.form-group:last-child {
margin-bottom: 0;
}
.form-label {
display: block;
font-weight: 500;
color: var(--text-primary);
margin-bottom: var(--spacing-xs);
font-size: 0.9rem;
}
.form-sublabel {
display: block;
color: var(--text-muted);
font-size: 0.8rem;
margin-top: 2px;
}
.form-input,
.form-select,
.form-textarea {
width: 100%;
padding: var(--spacing-sm) var(--spacing-md);
background-color: var(--bg-dark);
border: 1px solid var(--border-color);
border-radius: var(--border-radius);
color: var(--text-primary);
font-size: 0.9rem;
transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}
.form-input:focus,
.form-select:focus,
.form-textarea:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 3px var(--primary-light);
}
.form-textarea {
resize: vertical;
min-height: 100px;
}
.form-select[multiple] {
height: auto;
min-height: 120px;
}
.form-hint {
margin-top: var(--spacing-xs);
font-size: 0.8rem;
color: var(--text-muted);
}
.form-hint code {
background-color: var(--bg-dark);
padding: 2px 6px;
border-radius: 4px;
font-family: monospace;
font-size: 0.85em;
}
/* ===== Inline Toggle ===== */
.inline-toggle {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--spacing-md);
background-color: var(--bg-dark);
border-radius: var(--border-radius);
margin-bottom: var(--spacing-md);
}
.inline-toggle:last-child {
margin-bottom: 0;
}
.inline-toggle-label {
display: flex;
flex-direction: column;
}
.inline-toggle-title {
font-weight: 500;
color: var(--text-primary);
font-size: 0.9rem;
}
.inline-toggle-desc {
color: var(--text-muted);
font-size: 0.8rem;
margin-top: 2px;
}
/* ===== Status Messages ===== */
.status-message {
padding: var(--spacing-sm) var(--spacing-md);
border-radius: var(--border-radius);
font-size: 0.9rem;
font-weight: 500;
display: none;
}
.status-message.show {
display: block;
}
.status-message.success {
background-color: rgba(35, 134, 54, 0.15);
color: var(--success);
border: 1px solid var(--success);
}
.status-message.error {
background-color: rgba(248, 81, 73, 0.15);
color: var(--danger);
border: 1px solid var(--danger);
}
/* ===== Sub-sections ===== */
.sub-section {
margin-top: var(--spacing-lg);
padding-top: var(--spacing-lg);
border-top: 1px solid var(--border-color);
}
.sub-section-title {
font-size: 0.95rem;
font-weight: 600;
color: var(--text-primary);
margin-bottom: var(--spacing-md);
display: flex;
align-items: center;
gap: var(--spacing-sm);
}
/* ===== Variables Info Box ===== */
.variables-box {
background-color: var(--bg-dark);
border: 1px solid var(--border-color);
border-radius: var(--border-radius);
padding: var(--spacing-md);
margin-top: var(--spacing-md);
}
.variables-box-title {
font-size: 0.85rem;
font-weight: 600;
color: var(--text-secondary);
margin-bottom: var(--spacing-sm);
}
.variables-list {
display: flex;
flex-wrap: wrap;
gap: var(--spacing-sm);
}
.variable-tag {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 4px 8px;
background-color: var(--bg-card);
border-radius: 4px;
font-size: 0.8rem;
}
.variable-tag code {
color: var(--primary);
font-family: monospace;
}
.variable-tag span {
color: var(--text-muted);
}
/* ===== Mobile Sidebar Toggle ===== */
.mobile-toggle {
display: none;
position: fixed;
bottom: var(--spacing-lg);
right: var(--spacing-lg);
width: 56px;
height: 56px;
background-color: var(--primary);
border: none;
border-radius: 50%;
color: white;
font-size: 24px;
cursor: pointer;
box-shadow: var(--shadow-lg);
z-index: 200;
}
/* ===== Responsive ===== */
@media (max-width: 600px) {
body {
padding: 10px;
@media (max-width: 900px) {
.sidebar {
transform: translateX(-100%);
transition: transform var(--transition-normal);
}
form {
padding: 15px;
.sidebar.open {
transform: translateX(0);
}
.main-content {
margin-left: 0;
}
.mobile-toggle {
display: flex;
align-items: center;
justify-content: center;
}
.content-area {
padding: var(--spacing-md);
}
}
@media (max-width: 600px) {
.form-row {
grid-template-columns: 1fr;
}
.config-card-header {
flex-direction: column;
align-items: flex-start;
gap: var(--spacing-md);
}
}
+554 -397
View File
File diff suppressed because it is too large Load Diff
+19 -7
View File
@@ -1,8 +1,8 @@
const autoroleNewUserForm = document.getElementById("autorole-newuser-form");
const autoroleEnabled = document.getElementById("autorole-enabled");
const autoroleRole = document.getElementById("autorole-role");
const statusAutoroleForm = document.getElementById("status-autorole-form");
const saveAutorole = document.getElementById("save-autorole");
// Charger la config
fetch(`/api/bot/get-autorole-newuser-config/${guildId}`)
.then(res => res.json())
.then(cfg => {
@@ -10,9 +10,12 @@ fetch(`/api/bot/get-autorole-newuser-config/${guildId}`)
autoroleRole.value = cfg.roleId;
});
autoroleNewUserForm.addEventListener("submit", async e => {
e.preventDefault();
// Sauvegarder
saveAutorole.addEventListener("click", async () => {
saveAutorole.disabled = true;
saveAutorole.textContent = "Sauvegarde...";
try {
const res = await fetch("/api/bot/save-autorole-newuser-config", {
method: "POST",
headers: { "Content-Type": "application/json" },
@@ -23,7 +26,16 @@ autoroleNewUserForm.addEventListener("submit", async e => {
})
});
statusAutoroleForm.textContent = (await res.json()).success
? "Auto-rôle sauvegardé ✅"
: "Erreur ❌";
const data = await res.json();
if (data.success) {
showStatus("status-autorole-form", "Configuration sauvegardée ✅", "success");
} else {
showStatus("status-autorole-form", "Erreur lors de la sauvegarde ❌", "error");
}
} catch (error) {
showStatus("status-autorole-form", "Erreur de connexion ❌", "error");
}
saveAutorole.disabled = false;
saveAutorole.textContent = "Sauvegarder";
});
+22 -15
View File
@@ -1,17 +1,11 @@
const autoroleVocalForm = document.getElementById("autorole-vocal-form");
const autoroleVocalEnabled = document.getElementById("autorole-vocal-enabled");
const autoroleVocalRole = document.getElementById("autorole-vocal-role");
const excludeSelect = document.getElementById("autorole-vocal-exclude-channel");
const statusAutoroleVocalForm = document.getElementById("status-autorole-vocal-form");
const saveAutoroleVocal = document.getElementById("save-autorole-vocal");
fetch(`/api/bot/get-voice-channels/${guildId}`)
.then(res => res.json())
.then(channels => {
channels.forEach(c => {
excludeSelect.appendChild(new Option(`#${c.name}`, c.id));
});
return fetch(`/api/bot/get-autorole-vocal-config/${guildId}`);
})
// Charger la config après que les channels soient chargés
setTimeout(() => {
fetch(`/api/bot/get-autorole-vocal-config/${guildId}`)
.then(res => res.json())
.then(cfg => {
autoroleVocalEnabled.checked = cfg.enabled;
@@ -20,10 +14,14 @@ fetch(`/api/bot/get-voice-channels/${guildId}`)
opt.selected = cfg.excludeChannelIds?.includes(opt.value);
});
});
}, 500);
autoroleVocalForm.addEventListener("submit", async e => {
e.preventDefault();
// Sauvegarder
saveAutoroleVocal.addEventListener("click", async () => {
saveAutoroleVocal.disabled = true;
saveAutoroleVocal.textContent = "Sauvegarde...";
try {
const exclude = Array.from(excludeSelect.selectedOptions).map(o => o.value);
const res = await fetch("/api/bot/save-autorole-vocal-config", {
@@ -37,7 +35,16 @@ autoroleVocalForm.addEventListener("submit", async e => {
})
});
statusAutoroleVocalForm.textContent = (await res.json()).success
? "Auto-rôle vocal sauvegardé ✅"
: "Erreur ❌";
const data = await res.json();
if (data.success) {
showStatus("status-autorole-vocal-form", "Configuration sauvegardée ✅", "success");
} else {
showStatus("status-autorole-vocal-form", "Erreur lors de la sauvegarde ❌", "error");
}
} catch (error) {
showStatus("status-autorole-vocal-form", "Erreur de connexion ❌", "error");
}
saveAutoroleVocal.disabled = false;
saveAutoroleVocal.textContent = "Sauvegarder";
});
+17 -7
View File
@@ -1,4 +1,3 @@
const economyForm = document.getElementById("economy-form");
const economyEnabled = document.getElementById("economy-enabled");
const currencyName = document.getElementById("economy-currency-name");
const currencySymbol = document.getElementById("economy-currency-symbol");
@@ -42,7 +41,7 @@ const voiceMoneyMin = document.getElementById("economy-voice-money-min");
const voiceMoneyMax = document.getElementById("economy-voice-money-max");
const voiceMoneyInterval = document.getElementById("economy-voice-money-interval");
const statusEconomyForm = document.getElementById("status-economy-form");
const saveEconomy = document.getElementById("save-economy");
// Charger la config existante
fetch(`/api/bot/get-economy-config/${guildId}`)
@@ -94,9 +93,11 @@ fetch(`/api/bot/get-economy-config/${guildId}`)
.catch(console.error);
// Sauvegarder la config
economyForm.addEventListener("submit", async e => {
e.preventDefault();
saveEconomy.addEventListener("click", async () => {
saveEconomy.disabled = true;
saveEconomy.textContent = "Sauvegarde...";
try {
const res = await fetch("/api/bot/save-economy-config", {
method: "POST",
headers: { "Content-Type": "application/json" },
@@ -147,7 +148,16 @@ economyForm.addEventListener("submit", async e => {
})
});
statusEconomyForm.textContent = (await res.json()).success
? "Config économie sauvegardée ✅"
: "Erreur ❌";
const data = await res.json();
if (data.success) {
showStatus("status-economy-form", "Configuration sauvegardée ✅", "success");
} else {
showStatus("status-economy-form", "Erreur lors de la sauvegarde ❌", "error");
}
} catch (error) {
showStatus("status-economy-form", "Erreur de connexion ❌", "error");
}
saveEconomy.disabled = false;
saveEconomy.textContent = "Sauvegarder";
});
+23 -8
View File
@@ -1,20 +1,26 @@
const goodbyeForm = document.getElementById("goodbye-form");
const goodbyeEnabled = document.getElementById("goodbye-enabled");
const goodbyeChannel = document.getElementById("goodbye-channel");
const goodbyeMessage = document.getElementById("goodbye-message");
const statusGoodbyeForm = document.getElementById("status-goodbye-form");
const saveGoodbye = document.getElementById("save-goodbye");
// Message par défaut
const defaultGoodbyeMessage = "Au revoir **{user}**, on espère te revoir sur **{server}** ! 👋";
// Charger la config
fetch(`/api/bot/get-goodbye-config/${guildId}`)
.then(res => res.json())
.then(cfg => {
goodbyeEnabled.checked = cfg.enabled;
goodbyeChannel.value = cfg.channelId;
goodbyeMessage.value = cfg.message;
goodbyeMessage.value = cfg.message || defaultGoodbyeMessage;
});
goodbyeForm.addEventListener("submit", async e => {
e.preventDefault();
// Sauvegarder
saveGoodbye.addEventListener("click", async () => {
saveGoodbye.disabled = true;
saveGoodbye.textContent = "Sauvegarde...";
try {
const res = await fetch("/api/bot/save-goodbye-config", {
method: "POST",
headers: { "Content-Type": "application/json" },
@@ -26,7 +32,16 @@ goodbyeForm.addEventListener("submit", async e => {
})
});
statusGoodbyeForm.textContent = (await res.json()).success
? "Config au revoir sauvegardée ✅"
: "Erreur ❌";
const data = await res.json();
if (data.success) {
showStatus("status-goodbye-form", "Configuration sauvegardée ✅", "success");
} else {
showStatus("status-goodbye-form", "Erreur lors de la sauvegarde ❌", "error");
}
} catch (error) {
showStatus("status-goodbye-form", "Erreur de connexion ❌", "error");
}
saveGoodbye.disabled = false;
saveGoodbye.textContent = "Sauvegarder";
});
+62 -3
View File
@@ -1,12 +1,55 @@
window.guildId = window.location.pathname.split("/")[2];
// Nom du serveur
// Charger les infos du bot
fetch("/api/bot-info")
.then(res => res.json())
.then(bot => {
const botAvatar = document.getElementById("bot-avatar");
if (botAvatar) {
botAvatar.src = `https://cdn.discordapp.com/avatars/${bot.id}/${bot.avatar}.png`;
}
})
.catch(() => {});
// Charger les infos utilisateur
fetch("/api/user")
.then(res => {
if (!res.ok) throw new Error("Non connecté");
return res.json();
})
.then(user => {
const avatarUrl = `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.png`;
const userAvatar = document.getElementById("user-avatar");
const userName = document.getElementById("user-name");
if (userAvatar) userAvatar.src = avatarUrl;
if (userName) userName.textContent = user.username;
})
.catch(() => {
window.location.href = "/auth/login";
});
// Nom et icône du serveur
fetch("/api/guilds")
.then(res => res.json())
.then(guilds => {
const guild = guilds.find(g => g.id === guildId);
document.getElementById("guild-name").textContent =
guild ? `Dashboard : ${guild.name}` : "Serveur introuvable";
const guildName = document.getElementById("guild-name");
const guildIcon = document.getElementById("guild-icon");
const guildIdEl = document.getElementById("guild-id");
const breadcrumbGuild = document.getElementById("breadcrumb-guild");
if (guild) {
if (guildName) guildName.textContent = guild.name;
if (breadcrumbGuild) breadcrumbGuild.textContent = guild.name;
if (guildIdEl) guildIdEl.textContent = `ID: ${guild.id}`;
if (guildIcon && guild.icon) {
guildIcon.src = `https://cdn.discordapp.com/icons/${guild.id}/${guild.icon}.png`;
}
document.title = `${guild.name} - LazyBot`;
} else {
if (guildName) guildName.textContent = "Serveur introuvable";
}
});
// Channels texte
@@ -16,12 +59,26 @@ fetch(`/api/bot/get-text-channels/${guildId}`)
const welcome = document.getElementById("welcome-channel");
const goodbye = document.getElementById("goodbye-channel");
const levelAnnouncements = document.getElementById("level-announcements-channel");
const levelChannelRestrict = document.getElementById("level-channel-with-or-without-xp");
channels.forEach(c => {
const opt = new Option(`#${c.name}`, c.id);
welcome?.appendChild(opt);
goodbye?.appendChild(opt.cloneNode(true));
levelAnnouncements?.appendChild(opt.cloneNode(true));
levelChannelRestrict?.appendChild(opt.cloneNode(true));
});
});
// Channels vocaux
fetch(`/api/bot/get-voice-channels/${guildId}`)
.then(res => res.json())
.then(channels => {
const vocalExclude = document.getElementById("autorole-vocal-exclude-channel");
channels.forEach(c => {
const opt = new Option(`🔊 ${c.name}`, c.id);
vocalExclude?.appendChild(opt);
});
});
@@ -31,10 +88,12 @@ fetch(`/api/bot/get-roles/${guildId}`)
.then(roles => {
const newUser = document.getElementById("autorole-role");
const vocal = document.getElementById("autorole-vocal-role");
const levelRoleRestrict = document.getElementById("level-role-with-or-without-xp");
roles.forEach(r => {
const opt = new Option(r.name, r.id);
newUser?.appendChild(opt);
vocal?.appendChild(opt.cloneNode(true));
levelRoleRestrict?.appendChild(opt.cloneNode(true));
});
});
+25 -37
View File
@@ -1,4 +1,3 @@
const levelForm = document.getElementById("level-form");
const levelEnabled = document.getElementById("level-enabled");
const levelAnnouncementsEnabled = document.getElementById("level-announcement-enabled");
const levelAnnouncementsChannel = document.getElementById("level-announcements-channel");
@@ -18,43 +17,20 @@ const cooldownXpMessageSeconds = document.getElementById("level-xp-cooldown");
const gainXpOnVoice = document.getElementById("voice-xp-enabled");
const gainVoiceXpLowerBound = document.getElementById("level-xp-per-voice-min");
const gainVoiceXpUpperBound = document.getElementById("level-xp-per-voice-max");
const statusLevelForm = document.getElementById("status-level-form");
const saveLevel = document.getElementById("save-level");
// 1️⃣ RÔLES
fetch(`/api/bot/get-roles/${guildId}`)
.then(res => res.json())
.then(roles => {
roles.forEach(r => {
roleWithWithoutXp?.appendChild(new Option(r.name, r.id));
});
// Message par défaut
const defaultLevelMessage = "Félicitations {mention}, tu es maintenant niveau **{level}** ! 🎉";
// 2️⃣ SALONS TEXTE
return fetch(`/api/bot/get-text-channels/${guildId}`);
})
.then(res => res.json())
.then(textSalons => {
textSalons.forEach(c => {
salonWithWithoutXp?.appendChild(new Option(`#${c.name}`, c.id));
});
// 3️⃣ SALONS VOCAUX
return fetch(`/api/bot/get-voice-channels/${guildId}`);
})
.then(res => res.json())
.then(voiceSalons => {
voiceSalons.forEach(c => {
salonWithWithoutXp?.appendChild(new Option(`🔊 ${c.name}`, c.id));
});
// 4️⃣ CONFIG (APRÈS QUE TOUT EST CHARGÉ)
return fetch(`/api/bot/get-level-config/${guildId}`);
})
// Charger la config après que les channels/roles soient chargés
setTimeout(() => {
fetch(`/api/bot/get-level-config/${guildId}`)
.then(res => res.json())
.then(cfg => {
levelEnabled.checked = cfg.enabled;
levelAnnouncementsEnabled.checked = cfg.levelAnnouncementsEnabled;
levelAnnouncementsChannel.value = cfg.levelAnnouncementsChannelId;
levelAnnouncementsMessage.value = cfg.levelAnnouncementsMessage;
levelAnnouncementsMessage.value = cfg.levelAnnouncementsMessage || defaultLevelMessage;
xpCourbeType.value = cfg.xpCourbeType;
multiplierCourbeForLevel.value = cfg.multiplierCourbeForLevel;
@@ -81,11 +57,14 @@ fetch(`/api/bot/get-roles/${guildId}`)
gainVoiceXpUpperBound.value = cfg.gainVoiceXpUpperBound;
})
.catch(console.error);
}, 500);
// Sauvegarder
saveLevel.addEventListener("click", async () => {
saveLevel.disabled = true;
saveLevel.textContent = "Sauvegarde...";
levelForm.addEventListener("submit", async e => {
e.preventDefault();
try {
const res = await fetch("/api/bot/save-level-config", {
method: "POST",
headers: { "Content-Type": "application/json" },
@@ -113,7 +92,16 @@ levelForm.addEventListener("submit", async e => {
})
});
statusLevelForm.textContent = (await res.json()).success
? "Config niveaux sauvegardée ✅"
: "Erreur ❌";
const data = await res.json();
if (data.success) {
showStatus("status-level-form", "Configuration sauvegardée ✅", "success");
} else {
showStatus("status-level-form", "Erreur lors de la sauvegarde ❌", "error");
}
} catch (error) {
showStatus("status-level-form", "Erreur de connexion ❌", "error");
}
saveLevel.disabled = false;
saveLevel.textContent = "Sauvegarder";
});
+100
View File
@@ -0,0 +1,100 @@
// Navigation sidebar avec hash routing
document.addEventListener('DOMContentLoaded', () => {
const navItems = document.querySelectorAll('.nav-item[data-section]');
const sections = document.querySelectorAll('.config-section');
const sidebar = document.getElementById('sidebar');
const mobileToggle = document.getElementById('mobile-toggle');
// Fonction pour changer de section
function showSection(sectionId) {
// Masquer toutes les sections
sections.forEach(section => {
section.classList.remove('active');
});
// Retirer la classe active de tous les nav items
navItems.forEach(item => {
item.classList.remove('active');
});
// Afficher la section cible
const targetSection = document.getElementById(`section-${sectionId}`);
if (targetSection) {
targetSection.classList.add('active');
}
// Activer le nav item correspondant
const targetNav = document.querySelector(`.nav-item[data-section="${sectionId}"]`);
if (targetNav) {
targetNav.classList.add('active');
}
// Fermer la sidebar sur mobile
if (window.innerWidth <= 900) {
sidebar.classList.remove('open');
}
// Mettre à jour l'URL
window.location.hash = sectionId;
}
// Ajouter les listeners sur les nav items
navItems.forEach(item => {
item.addEventListener('click', (e) => {
e.preventDefault();
const sectionId = item.dataset.section;
showSection(sectionId);
});
});
// Gérer le hash au chargement de la page
function handleHash() {
const hash = window.location.hash.slice(1); // Enlever le #
if (hash && document.getElementById(`section-${hash}`)) {
showSection(hash);
} else {
// Par défaut, afficher la première section
const firstNavItem = navItems[0];
if (firstNavItem) {
showSection(firstNavItem.dataset.section);
}
}
}
// Écouter les changements de hash
window.addEventListener('hashchange', handleHash);
// Charger la section initiale
handleHash();
// Toggle sidebar sur mobile
if (mobileToggle) {
mobileToggle.addEventListener('click', () => {
sidebar.classList.toggle('open');
});
}
// Fermer la sidebar en cliquant à l'extérieur sur mobile
document.addEventListener('click', (e) => {
if (window.innerWidth <= 900) {
if (!sidebar.contains(e.target) && !mobileToggle.contains(e.target)) {
sidebar.classList.remove('open');
}
}
});
});
// Helper function pour afficher les messages de statut
window.showStatus = function(elementId, message, type = 'success') {
const element = document.getElementById(elementId);
if (element) {
element.textContent = message;
element.className = `status-message show ${type}`;
// Masquer après 5 secondes
setTimeout(() => {
element.classList.remove('show');
}, 5000);
}
};
+23 -8
View File
@@ -1,20 +1,26 @@
const welcomeForm = document.getElementById("welcome-form");
const welcomeEnabled = document.getElementById("welcome-enabled");
const welcomeChannel = document.getElementById("welcome-channel");
const welcomeMessage = document.getElementById("welcome-message");
const statusWelcomeForm = document.getElementById("status-welcome-form");
const saveWelcome = document.getElementById("save-welcome");
// Message par défaut
const defaultWelcomeMessage = "Bienvenue {mention} sur **{server}** ! 🎉";
// Charger la config
fetch(`/api/bot/get-welcome-config/${guildId}`)
.then(res => res.json())
.then(cfg => {
welcomeEnabled.checked = cfg.enabled;
welcomeChannel.value = cfg.channelId;
welcomeMessage.value = cfg.message;
welcomeMessage.value = cfg.message || defaultWelcomeMessage;
});
welcomeForm.addEventListener("submit", async e => {
e.preventDefault();
// Sauvegarder
saveWelcome.addEventListener("click", async () => {
saveWelcome.disabled = true;
saveWelcome.textContent = "Sauvegarde...";
try {
const res = await fetch("/api/bot/save-welcome-config", {
method: "POST",
headers: { "Content-Type": "application/json" },
@@ -26,7 +32,16 @@ welcomeForm.addEventListener("submit", async e => {
})
});
statusWelcomeForm.textContent = (await res.json()).success
? "Config bienvenue sauvegardée ✅"
: "Erreur ❌";
const data = await res.json();
if (data.success) {
showStatus("status-welcome-form", "Configuration sauvegardée ✅", "success");
} else {
showStatus("status-welcome-form", "Erreur lors de la sauvegarde ❌", "error");
}
} catch (error) {
showStatus("status-welcome-form", "Erreur de connexion ❌", "error");
}
saveWelcome.disabled = false;
saveWelcome.textContent = "Sauvegarder";
});
+361 -98
View File
@@ -1,130 +1,393 @@
/* ===== Reset minimal ===== */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
}
@import url('global.css');
body {
background-color: #1e1f29; /* fond sombre type Discord */
color: #ffffff;
line-height: 1.6;
/* ===== Hero Section ===== */
.hero {
display: flex;
flex-direction: column;
align-items: center; /* centre horizontalement */
justify-content: flex-start; /* commencer depuis le haut */
padding: 20px;
align-items: center;
justify-content: center;
text-align: center;
padding: var(--spacing-2xl) var(--spacing-md);
min-height: 70vh;
background: linear-gradient(135deg, var(--bg-dark) 0%, #0a1628 50%, var(--bg-dark) 100%);
position: relative;
overflow: hidden;
}
.hero::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: radial-gradient(circle at 50% 50%, rgba(88, 101, 242, 0.1) 0%, transparent 50%);
pointer-events: none;
}
.hero-content {
position: relative;
z-index: 1;
max-width: 800px;
}
.hero-logo {
width: 120px;
height: 120px;
border-radius: 50%;
margin-bottom: var(--spacing-lg);
border: 4px solid var(--primary);
box-shadow: var(--shadow-lg), 0 0 40px rgba(88, 101, 242, 0.3);
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { box-shadow: var(--shadow-lg), 0 0 40px rgba(88, 101, 242, 0.3); }
50% { box-shadow: var(--shadow-lg), 0 0 60px rgba(88, 101, 242, 0.5); }
}
.hero-title {
font-size: 3.5rem;
font-weight: 800;
margin-bottom: var(--spacing-md);
background: linear-gradient(135deg, var(--text-primary) 0%, var(--primary) 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.hero-subtitle {
font-size: 1.25rem;
color: var(--text-secondary);
margin-bottom: var(--spacing-xl);
max-width: 600px;
line-height: 1.6;
}
.hero-buttons {
display: flex;
gap: var(--spacing-md);
flex-wrap: wrap;
justify-content: center;
}
/* ===== Navigation ===== */
nav {
.navbar {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 1000;
background-color: rgba(22, 27, 34, 0.95);
backdrop-filter: blur(10px);
border-bottom: 1px solid var(--border-color);
padding: var(--spacing-md) 0;
}
.navbar-container {
display: flex;
align-items: center;
justify-content: space-between;
align-items: center;
background-color: #2f3136;
padding: 10px 20px;
border-radius: 10px;
margin-bottom: 30px;
width: 100%;
max-width: 800px; /* limite largeur nav */
max-width: 1200px;
margin: 0 auto;
padding: 0 var(--spacing-md);
}
nav a {
color: #ffffff;
text-decoration: none;
margin-right: 15px;
font-weight: 500;
transition: color 0.2s;
}
nav a:hover {
color: #5865f2;
}
#profil {
.navbar-brand {
display: flex;
align-items: center;
gap: 10px;
gap: var(--spacing-sm);
font-size: 1.25rem;
font-weight: 700;
color: var(--text-primary);
text-decoration: none;
}
#profil img {
width: 40px;
height: 40px;
.navbar-brand:hover {
color: var(--text-primary);
text-decoration: none;
}
.navbar-brand img {
width: 32px;
height: 32px;
border-radius: 50%;
}
#logout {
background-color: #5865f2;
color: white;
padding: 5px 12px;
border-radius: 6px;
.navbar-nav {
display: flex;
align-items: center;
gap: var(--spacing-lg);
}
.navbar-link {
color: var(--text-secondary);
font-weight: 500;
text-decoration: none;
transition: color var(--transition-fast);
}
.navbar-link:hover {
color: var(--text-primary);
text-decoration: none;
}
.navbar-user {
display: flex;
align-items: center;
gap: var(--spacing-sm);
padding: var(--spacing-xs) var(--spacing-sm);
background-color: var(--bg-card);
border-radius: var(--border-radius);
border: 1px solid var(--border-color);
}
.navbar-user img {
width: 28px;
height: 28px;
border-radius: 50%;
}
.navbar-user span {
color: var(--text-primary);
font-weight: 500;
}
.navbar-actions {
display: flex;
align-items: center;
gap: var(--spacing-sm);
}
/* ===== Features Section ===== */
.features {
padding: var(--spacing-2xl) var(--spacing-md);
background-color: var(--bg-card);
}
.section-header {
text-align: center;
margin-bottom: var(--spacing-2xl);
}
.section-header h2 {
font-size: 2.25rem;
margin-bottom: var(--spacing-sm);
color: var(--text-primary);
}
.section-header p {
font-size: 1.1rem;
color: var(--text-secondary);
max-width: 600px;
margin: 0 auto;
}
.features-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: var(--spacing-lg);
max-width: 1200px;
margin: 0 auto;
}
.feature-card {
background-color: var(--bg-dark);
border: 1px solid var(--border-color);
border-radius: var(--border-radius-lg);
padding: var(--spacing-xl);
transition: transform var(--transition-normal), border-color var(--transition-normal), box-shadow var(--transition-normal);
}
.feature-card:hover {
transform: translateY(-4px);
border-color: var(--primary);
box-shadow: 0 8px 30px rgba(88, 101, 242, 0.15);
}
.feature-icon {
width: 56px;
height: 56px;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, var(--primary), #7289da);
border-radius: var(--border-radius);
font-size: 28px;
margin-bottom: var(--spacing-md);
}
.feature-card h3 {
font-size: 1.25rem;
margin-bottom: var(--spacing-sm);
color: var(--text-primary);
}
.feature-card p {
font-size: 0.95rem;
color: var(--text-secondary);
line-height: 1.6;
}
/* ===== Stats Section ===== */
.stats {
padding: var(--spacing-2xl) var(--spacing-md);
text-align: center;
background-color: var(--bg-dark);
}
.stats-grid {
display: flex;
justify-content: center;
gap: var(--spacing-2xl);
flex-wrap: wrap;
max-width: 800px;
margin: 0 auto;
}
.stat-item {
display: flex;
flex-direction: column;
align-items: center;
padding: var(--spacing-lg);
}
.stat-number {
font-size: 3.5rem;
font-weight: 800;
background: linear-gradient(135deg, var(--primary), #7289da);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
line-height: 1;
}
.stat-label {
color: var(--text-secondary);
font-size: 1.1rem;
margin-top: var(--spacing-sm);
font-weight: 500;
}
/* ===== CTA Section ===== */
.cta {
padding: var(--spacing-2xl) var(--spacing-md);
text-align: center;
background: linear-gradient(135deg, rgba(88, 101, 242, 0.1) 0%, transparent 100%);
}
.cta h2 {
font-size: 2rem;
margin-bottom: var(--spacing-md);
color: var(--text-primary);
}
.cta p {
font-size: 1.1rem;
color: var(--text-secondary);
margin-bottom: var(--spacing-xl);
}
/* ===== Footer ===== */
.footer {
padding: var(--spacing-xl) var(--spacing-md);
border-top: 1px solid var(--border-color);
text-align: center;
background-color: var(--bg-card);
}
.footer-content {
max-width: 1200px;
margin: 0 auto;
display: flex;
flex-direction: column;
align-items: center;
gap: var(--spacing-md);
}
.footer-links {
display: flex;
gap: var(--spacing-lg);
}
.footer-links a {
color: var(--text-secondary);
text-decoration: none;
transition: color var(--transition-fast);
}
.footer-links a:hover {
color: var(--primary);
}
.footer-copyright {
color: var(--text-muted);
font-size: 0.9rem;
transition: background 0.2s;
}
#logout:hover {
background-color: #4752c4;
}
/* ===== Titres ===== */
h1, h2 {
text-align: center; /* centre le texte */
}
h1 {
font-size: 3rem;
margin-bottom: 20px;
color: #ffffff;
}
h2 {
font-size: 1.8rem;
margin: 25px 0 10px 0;
color: #ffffff;
border-bottom: 2px solid #5865f2;
display: inline-block;
padding-bottom: 5px;
}
/* ===== Bouton d'invitation ===== */
#invite-link {
display: inline-block;
background-color: #5865f2;
/* ===== Login Button ===== */
.btn-login {
display: inline-flex;
align-items: center;
gap: var(--spacing-sm);
background-color: var(--primary);
color: white;
padding: 10px 20px;
border-radius: 8px;
padding: var(--spacing-sm) var(--spacing-md);
border-radius: var(--border-radius);
text-decoration: none;
font-weight: 600;
transition: background 0.2s;
margin-bottom: 20px;
transition: background-color var(--transition-fast);
}
#invite-link:hover {
background-color: #4752c4;
}
/* ===== Listes ===== */
ul {
list-style: disc inside;
margin: 10px 0 20px 0;
max-width: 700px;
text-align: left;
}
li {
margin-bottom: 10px;
.btn-login:hover {
background-color: var(--primary-hover);
color: white;
text-decoration: none;
}
/* ===== Responsive ===== */
@media (max-width: 600px) {
nav {
flex-direction: column;
align-items: center;
gap: 10px;
@media (max-width: 768px) {
.hero-title {
font-size: 2.5rem;
}
#profil {
gap: 5px;
.hero-subtitle {
font-size: 1rem;
}
.navbar-nav {
gap: var(--spacing-md);
}
.stats-grid {
gap: var(--spacing-xl);
}
.stat-number {
font-size: 2.5rem;
}
.section-header h2 {
font-size: 1.75rem;
}
}
@media (max-width: 480px) {
.hero-buttons {
flex-direction: column;
width: 100%;
}
.hero-buttons .btn {
width: 100%;
}
.navbar-nav .navbar-link {
display: none;
}
}
/* Body padding for fixed navbar */
body {
padding-top: 65px;
}
+159 -35
View File
@@ -1,62 +1,186 @@
<!DOCTYPE html>
<html>
<html lang="fr">
<head>
<title>LazyBot - Bot Discord</title>
<title>LazyBot - Bot Discord Multifonction</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="LazyBot est un bot Discord puissant avec système de niveaux, économie, messages personnalisés et bien plus encore.">
<link rel="stylesheet" href="/index.css">
</head>
<body>
<nav>
<a href="/">Accueil</a>
<a href="/dashboard">Tableau de bord</a>
<div id="profil">
<img id="avatar" src="" alt="Avatar">
<span id="username"></span>
<a id="logout" href="/auth/logout">Se déconnecter</a>
<!-- Navigation -->
<nav class="navbar">
<div class="navbar-container">
<a href="/" class="navbar-brand">
<img id="bot-avatar" src="" alt="LazyBot">
<span>LazyBot</span>
</a>
<div class="navbar-nav">
<a href="/" class="navbar-link">Accueil</a>
<a href="/dashboard" class="navbar-link">Dashboard</a>
<div id="auth-section">
<a href="/auth/login" class="btn btn-primary btn-sm">Connexion</a>
</div>
</div>
</div>
</nav>
<h1>LazyBot</h1>
<a id="invite-link" href="#">Ajouter à Discord</a>
<br><br>
<h2>Ajout du bot</h2>
<ul>
<li>Ajoutez le bot à votre serveur Discord en cliquant sur le lien ci-dessus.</li>
<li>Utilisez le tableau de bord pour configurer les paramètres du bot sur vos serveurs.</li>
<li>Profitez des fonctionnalités offertes par LazyBot pour améliorer votre expérience Discord !</li>
</ul>
<!-- Hero Section -->
<section class="hero">
<div class="hero-content">
<img id="hero-logo" class="hero-logo" src="" alt="LazyBot Logo">
<h1 class="hero-title">LazyBot</h1>
<p class="hero-subtitle">
Un bot Discord complet avec système de niveaux, économie, messages de bienvenue personnalisés,
rôles automatiques et un dashboard intuitif pour gérer votre serveur.
</p>
<div class="hero-buttons">
<a id="invite-link" href="#" class="btn btn-primary btn-lg">
Ajouter à Discord
</a>
<a href="/dashboard" class="btn btn-secondary btn-lg">
⚙️ Dashboard
</a>
</div>
</div>
</section>
<!-- Features Section -->
<section class="features">
<div class="section-header">
<h2>Fonctionnalités</h2>
<ul>
<li>Messages de bienvenue et d'au revoir personnalisables</li>
<li>Rôles automatiques pour les nouveaux membres et les utilisateurs en vocal</li>
<li>Et bien plus encore...</li>
</body>
<script>
<p>Tout ce dont vous avez besoin pour votre serveur Discord</p>
</div>
<div class="features-grid">
<div class="feature-card">
<div class="feature-icon">📈</div>
<h3>Système de Niveaux</h3>
<p>Récompensez l'activité de vos membres avec un système d'XP complet. Messages, vocal, courbes personnalisables et annonces automatiques.</p>
</div>
<div class="feature-card">
<div class="feature-icon">💰</div>
<h3>Économie</h3>
<p>Un système économique complet avec monnaie personnalisable, commandes daily, work, crime, steal, et transactions entre membres.</p>
</div>
<div class="feature-card">
<div class="feature-icon">👋</div>
<h3>Messages Personnalisés</h3>
<p>Accueillez les nouveaux membres et dites au revoir avec des messages entièrement personnalisables avec variables.</p>
</div>
<div class="feature-card">
<div class="feature-icon">🎭</div>
<h3>Rôles Automatiques</h3>
<p>Attribuez automatiquement des rôles aux nouveaux membres ou aux utilisateurs en vocal. Configurez les salons à exclure.</p>
</div>
<div class="feature-card">
<div class="feature-icon">⚙️</div>
<h3>Dashboard Intuitif</h3>
<p>Configurez tout depuis un dashboard web moderne et facile à utiliser. Pas besoin de commandes compliquées.</p>
</div>
<div class="feature-card">
<div class="feature-icon">🛡️</div>
<h3>Commandes Admin</h3>
<p>Gérez les niveaux et l'économie de vos membres avec des commandes d'administration dédiées.</p>
</div>
</div>
</section>
<!-- Stats Section -->
<section class="stats">
<div class="section-header">
<h2>Statistiques</h2>
</div>
<div class="stats-grid">
<div class="stat-item">
<span id="stat-servers" class="stat-number">-</span>
<span class="stat-label">Serveurs</span>
</div>
<div class="stat-item">
<span id="stat-users" class="stat-number">-</span>
<span class="stat-label">Utilisateurs</span>
</div>
<div class="stat-item">
<span id="stat-commands" class="stat-number">20+</span>
<span class="stat-label">Commandes</span>
</div>
</div>
</section>
<!-- CTA Section -->
<section class="cta">
<h2>Prêt à améliorer votre serveur ?</h2>
<p>Ajoutez LazyBot dès maintenant et découvrez toutes ses fonctionnalités.</p>
<a id="invite-link-2" href="#" class="btn btn-primary btn-lg">
Ajouter à Discord
</a>
</section>
<!-- Footer -->
<footer class="footer">
<div class="footer-content">
<div class="footer-links">
<a href="/dashboard">Dashboard</a>
<a href="https://discord.com" target="_blank">Support</a>
</div>
<p class="footer-copyright">
© 2024 LazyBot. Fait avec ❤️ pour Discord.
</p>
</div>
</footer>
<script>
// Charger le lien d'invitation
fetch("/invite-bot")
.then(res => res.json())
.then(data => {
const link = document.getElementById("invite-link");
link.href = data.url; // met le lien dynamique
document.getElementById("invite-link").href = data.url;
document.getElementById("invite-link-2").href = data.url;
})
.catch(() => console.log("Impossible de récupérer le lien du bot."));
// Charger les infos du bot (avatar)
fetch("/api/bot-info")
.then(res => res.json())
.then(bot => {
const avatarUrl = `https://cdn.discordapp.com/avatars/${bot.id}/${bot.avatar}.png`;
document.getElementById("bot-avatar").src = avatarUrl;
document.getElementById("hero-logo").src = avatarUrl;
// Stats
if (bot.guildCount) {
document.getElementById("stat-servers").textContent = bot.guildCount;
}
if (bot.userCount) {
document.getElementById("stat-users").textContent = bot.userCount;
}
})
.catch(() => {
console.log("Impossible de récupérer le lien du bot.");
// Utiliser un placeholder si pas d'info bot
document.getElementById("bot-avatar").src = "https://cdn.discordapp.com/embed/avatars/0.png";
document.getElementById("hero-logo").src = "https://cdn.discordapp.com/embed/avatars/0.png";
});
// Vérifier si l'utilisateur est connecté
fetch("/api/user")
.then(res => {
if (!res.ok) throw new Error("Utilisateur non connecté"); // gère les erreurs HTTP
if (!res.ok) throw new Error("Non connecté");
return res.json();
})
.then(user => {
document.getElementById("username").textContent = `${user.username}`;
document.getElementById("avatar").src = `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.png`;
const authSection = document.getElementById("auth-section");
authSection.innerHTML = `
<div class="navbar-actions">
<div class="navbar-user">
<img src="https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.png" alt="Avatar">
<span>${user.username}</span>
</div>
<a href="/auth/logout" class="btn btn-secondary btn-sm">Déconnexion</a>
</div>
`;
})
.catch(() => {
const profilDiv = document.getElementById("profil");
profilDiv.style.display = "none";
// Garder le bouton de connexion par défaut
});
</script>
</script>
</body>
</html>
+20
View File
@@ -4,6 +4,26 @@ const loadSlashCommands = require("../slash_commands");
module.exports = (app, db, client) => {
// --- Bot info ---
router.get("/bot-info", (req, res) => {
const bot = client.user;
if (!bot) return res.status(500).json({ error: "Bot non connecté" });
let userCount = 0;
client.guilds.cache.forEach(g => {
userCount += g.memberCount;
});
res.json({
id: bot.id,
username: bot.username,
discriminator: bot.discriminator,
avatar: bot.avatar,
guildCount: client.guilds.cache.size,
userCount: userCount
});
});
// --- User info ---
router.get("/user", (req, res) => {
if (req.session.user) res.json(req.session.user);
+5
View File
@@ -8,6 +8,11 @@ const CLIENT_SECRET = process.env.CLIENT_SECRET;
const REDIRECT_URI = process.env.REDIRECT_URI;
module.exports = (app, db, client) => {
// --- Redirection login ---
router.get("/login", (req, res) => {
res.redirect("/auth/discord");
});
// --- Connexion Discord ---
router.get("/discord", (req, res) => {
const url = `https://discord.com/api/oauth2/authorize?client_id=${CLIENT_ID}&redirect_uri=${encodeURIComponent(REDIRECT_URI)}&response_type=code&scope=identify%20guilds`;