sbi-book / index.html
Hubertoo's picture
Inscription ne fonctionne pas - Initial Deployment
9b7ce94 verified
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SBI BOOK - Réseau Social</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
/* Custom CSS for elements that can't be done with Tailwind */
.online-status::after {
content: '';
position: absolute;
bottom: 0;
right: 0;
width: 12px;
height: 12px;
background-color: #4ade80;
border-radius: 50%;
border: 2px solid white;
}
.file-input-label {
cursor: pointer;
transition: all 0.3s;
}
.file-input-label:hover {
transform: scale(1.05);
}
.comment-reply {
margin-left: 40px;
border-left: 2px solid #e5e7eb;
padding-left: 10px;
}
.audio-recorder {
animation: pulse 2s infinite;
}
@keyframes pulse {
0% { box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.7); }
70% { box-shadow: 0 0 0 10px rgba(59, 130, 246, 0); }
100% { box-shadow: 0 0 0 0 rgba(59, 130, 246, 0); }
}
.modal {
transition: opacity 0.3s ease, transform 0.3s ease;
}
.modal.hidden {
opacity: 0;
transform: scale(0.9);
pointer-events: none;
}
</style>
</head>
<body class="bg-gray-100 min-h-screen flex flex-col">
<!-- Auth Modal -->
<div id="authModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div class="bg-white rounded-lg p-6 w-full max-w-md">
<div class="flex justify-between items-center mb-4">
<h2 class="text-2xl font-bold text-blue-600">SBI BOOK</h2>
<button id="closeAuthModal" class="text-gray-500 hover:text-gray-700">
<i class="fas fa-times"></i>
</button>
</div>
<div id="authTabs" class="flex border-b mb-4">
<button id="loginTab" class="px-4 py-2 font-medium text-blue-600 border-b-2 border-blue-600">Connexion</button>
<button id="registerTab" class="px-4 py-2 font-medium text-gray-500">Inscription</button>
</div>
<!-- Login Form -->
<div id="loginForm">
<div class="mb-4">
<label class="block text-gray-700 mb-2" for="loginUsername">Nom d'utilisateur</label>
<input type="text" id="loginUsername" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<div class="mb-4 relative">
<label class="block text-gray-700 mb-2" for="loginPassword">Mot de passe</label>
<input type="password" id="loginPassword" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
<button id="toggleLoginPassword" class="absolute right-3 top-9 text-gray-500">
<i class="fas fa-eye"></i>
</button>
</div>
<div class="mb-4 flex justify-between items-center">
<button id="forgotPassword" class="text-blue-600 text-sm">Mot de passe oublié ?</button>
</div>
<button id="loginBtn" class="w-full bg-blue-600 text-white py-2 rounded-lg hover:bg-blue-700 transition">Se connecter</button>
</div>
<!-- Register Form -->
<div id="registerForm" class="hidden">
<div class="mb-4">
<label class="block text-gray-700 mb-2" for="fullName">Nom complet*</label>
<input type="text" id="fullName" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" required>
</div>
<div class="mb-4">
<label class="block text-gray-700 mb-2" for="birthDate">Date de naissance*</label>
<input type="date" id="birthDate" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" required>
</div>
<div class="mb-4">
<label class="block text-gray-700 mb-2" for="location">Lieu de résidence*</label>
<input type="text" id="location" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" required>
</div>
<div class="mb-4">
<label class="block text-gray-700 mb-2" for="username">Nom d'utilisateur</label>
<input type="text" id="username" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<div class="mb-4 relative">
<label class="block text-gray-700 mb-2" for="password">Mot de passe*</label>
<input type="password" id="password" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" required>
<button id="toggleRegisterPassword" class="absolute right-3 top-9 text-gray-500">
<i class="fas fa-eye"></i>
</button>
</div>
<button id="registerBtn" class="w-full bg-blue-600 text-white py-2 rounded-lg hover:bg-blue-700 transition">S'inscrire</button>
</div>
<!-- Forgot Password Form -->
<div id="forgotPasswordForm" class="hidden">
<h3 class="text-lg font-medium mb-4">Réinitialiser le mot de passe</h3>
<div class="mb-4">
<label class="block text-gray-700 mb-2" for="forgotUsername">Nom d'utilisateur</label>
<input type="text" id="forgotUsername" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<div class="mb-4">
<label class="block text-gray-700 mb-2" for="forgotEmail">Email associé</label>
<input type="email" id="forgotEmail" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<button id="resetPasswordBtn" class="w-full bg-blue-600 text-white py-2 rounded-lg hover:bg-blue-700 transition mb-2">Réinitialiser</button>
<button id="backToLogin" class="w-full bg-gray-200 text-gray-800 py-2 rounded-lg hover:bg-gray-300 transition">Retour</button>
</div>
</div>
</div>
<!-- Main App (hidden until login) -->
<div id="app" class="hidden flex-1 flex flex-col">
<!-- Header -->
<header class="bg-white shadow-sm sticky top-0 z-10">
<div class="container mx-auto px-4 py-3 flex justify-between items-center">
<div class="flex items-center">
<h1 class="text-2xl font-bold text-blue-600">SBI BOOK</h1>
</div>
<div class="flex items-center space-x-4">
<div id="userMenu" class="relative">
<button id="userMenuButton" class="flex items-center space-x-2 focus:outline-none">
<div class="w-10 h-10 rounded-full bg-gray-300 overflow-hidden relative">
<img id="userAvatar" src="" alt="Profile" class="w-full h-full object-cover">
<div class="online-status"></div>
</div>
<span id="userNameDisplay" class="font-medium"></span>
</button>
<div id="userDropdown" class="hidden absolute right-0 mt-2 w-48 bg-white rounded-md shadow-lg py-1 z-20">
<a href="#" id="profileLink" class="block px-4 py-2 text-gray-800 hover:bg-gray-100">Profil</a>
<a href="#" id="settingsLink" class="block px-4 py-2 text-gray-800 hover:bg-gray-100">Paramètres</a>
<a href="#" id="logoutLink" class="block px-4 py-2 text-gray-800 hover:bg-gray-100">Déconnexion</a>
</div>
</div>
</div>
</div>
</header>
<!-- Create Post Section -->
<div class="container mx-auto px-4 py-4">
<div class="bg-white rounded-lg shadow p-4 mb-4">
<div class="flex items-start space-x-3">
<div class="w-12 h-12 rounded-full bg-gray-300 overflow-hidden relative">
<img id="currentUserAvatar" src="" alt="Profile" class="w-full h-full object-cover">
<div class="online-status"></div>
</div>
<div class="flex-1">
<textarea id="postContent" placeholder="À quoi pensez-vous ?" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 resize-none" rows="2"></textarea>
<div id="postPreview" class="mt-2 hidden"></div>
<div class="flex justify-between items-center mt-3">
<div class="flex space-x-2">
<label for="postImage" class="file-input-label text-green-600 cursor-pointer">
<i class="fas fa-image"></i> Photo
</label>
<input type="file" id="postImage" accept="image/*" class="hidden">
<label for="postVideo" class="file-input-label text-blue-600 cursor-pointer">
<i class="fas fa-video"></i> Vidéo
</label>
<input type="file" id="postVideo" accept="video/*" class="hidden">
<label for="postAudio" class="file-input-label text-purple-600 cursor-pointer">
<i class="fas fa-music"></i> Audio
</label>
<input type="file" id="postAudio" accept="audio/*" class="hidden">
<button id="startRecording" class="text-red-600">
<i class="fas fa-microphone"></i> Vocal
</button>
</div>
<button id="postSubmit" class="bg-blue-600 text-white px-4 py-1 rounded-lg hover:bg-blue-700 transition">Publier</button>
</div>
</div>
</div>
</div>
</div>
<!-- Main Content -->
<div class="container mx-auto px-4 flex flex-col md:flex-row gap-4 flex-1">
<!-- Left Sidebar -->
<div class="w-full md:w-1/4 lg:w-1/5">
<div class="bg-white rounded-lg shadow p-4 mb-4">
<h3 class="font-bold text-lg mb-3">Menu</h3>
<ul class="space-y-2">
<li><a href="#" class="flex items-center space-x-2 text-blue-600"><i class="fas fa-home"></i> <span>Fil d'actualité</span></a></li>
<li><a href="#" class="flex items-center space-x-2"><i class="fas fa-user-friends"></i> <span>Amis</span></a></li>
<li><a href="#" id="groupsLink" class="flex items-center space-x-2"><i class="fas fa-users"></i> <span>Groupes</span></a></li>
<li><a href="#" class="flex items-center space-x-2"><i class="fas fa-envelope"></i> <span>Messages</span></a></li>
</ul>
</div>
<div class="bg-white rounded-lg shadow p-4">
<h3 class="font-bold text-lg mb-3">Groupes</h3>
<div id="groupsList" class="space-y-2">
<!-- Groups will be populated here -->
</div>
<button id="createGroupBtn" class="mt-3 w-full bg-gray-200 text-gray-800 py-1 rounded-lg hover:bg-gray-300 transition">+ Créer un groupe</button>
</div>
</div>
<!-- Main Feed -->
<div class="w-full md:w-2/4 lg:w-3/5">
<div id="postsContainer" class="space-y-4">
<!-- Posts will be populated here -->
</div>
</div>
<!-- Right Sidebar -->
<div class="w-full md:w-1/4 lg:w-1/5">
<div class="bg-white rounded-lg shadow p-4 mb-4">
<h3 class="font-bold text-lg mb-3">Contacts</h3>
<div id="contactsList" class="space-y-3">
<!-- Contacts will be populated here -->
</div>
</div>
</div>
</div>
<!-- Footer -->
<footer class="bg-white shadow-md py-3 mt-5">
<div class="container mx-auto px-4">
<div class="flex justify-around">
<a href="#" class="flex flex-col items-center text-gray-700">
<i class="fas fa-comments text-xl"></i>
<span class="text-xs mt-1">Discussions</span>
</a>
<a href="#" class="flex flex-col items-center text-blue-600">
<i class="fas fa-newspaper text-xl"></i>
<span class="text-xs mt-1">Actus</span>
</a>
<a href="#" class="flex flex-col items-center text-gray-700">
<i class="fas fa-users text-xl"></i>
<span class="text-xs mt-1">Groupes</span>
</a>
</div>
</div>
</footer>
</div>
<!-- Create Group Modal -->
<div id="createGroupModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
<div class="bg-white rounded-lg p-6 w-full max-w-md">
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-bold">Créer un groupe</h2>
<button id="closeGroupModal" class="text-gray-500 hover:text-gray-700">
<i class="fas fa-times"></i>
</button>
</div>
<div class="mb-4">
<label class="block text-gray-700 mb-2" for="groupName">Nom du groupe*</label>
<input type="text" id="groupName" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" required>
</div>
<div class="mb-4">
<label class="block text-gray-700 mb-2" for="groupDescription">Description</label>
<textarea id="groupDescription" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" rows="3"></textarea>
</div>
<div class="mb-4">
<label class="block text-gray-700 mb-2" for="groupImage">Image de couverture</label>
<input type="file" id="groupImage" accept="image/*" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<button id="createGroupSubmit" class="w-full bg-blue-600 text-white py-2 rounded-lg hover:bg-blue-700 transition">Créer</button>
</div>
</div>
<!-- Settings Modal -->
<div id="settingsModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
<div class="bg-white rounded-lg p-6 w-full max-w-md">
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-bold">Paramètres</h2>
<button id="closeSettingsModal" class="text-gray-500 hover:text-gray-700">
<i class="fas fa-times"></i>
</button>
</div>
<div class="mb-4">
<h3 class="font-medium mb-2">Profil</h3>
<div class="flex items-center space-x-4 mb-3">
<div class="w-16 h-16 rounded-full bg-gray-300 overflow-hidden relative">
<img id="settingsAvatar" src="" alt="Profile" class="w-full h-full object-cover">
</div>
<div>
<label for="settingsAvatarInput" class="text-blue-600 cursor-pointer">Changer la photo</label>
<input type="file" id="settingsAvatarInput" accept="image/*" class="hidden">
</div>
</div>
<div class="mb-3">
<label class="block text-gray-700 mb-1" for="settingsFullName">Nom complet</label>
<input type="text" id="settingsFullName" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<div class="mb-3">
<label class="block text-gray-700 mb-1" for="settingsUsername">Nom d'utilisateur</label>
<input type="text" id="settingsUsername" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<div class="mb-3">
<label class="block text-gray-700 mb-1" for="settingsLocation">Lieu de résidence</label>
<input type="text" id="settingsLocation" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
</div>
<div class="mb-4">
<h3 class="font-medium mb-2">Sécurité</h3>
<button id="changePasswordBtn" class="w-full text-left py-2 px-3 bg-gray-100 rounded-lg mb-2 hover:bg-gray-200">Changer le mot de passe</button>
<button id="blockedUsersBtn" class="w-full text-left py-2 px-3 bg-gray-100 rounded-lg hover:bg-gray-200">Utilisateurs bloqués</button>
</div>
<button id="saveSettings" class="w-full bg-blue-600 text-white py-2 rounded-lg hover:bg-blue-700 transition">Enregistrer</button>
</div>
</div>
<!-- Change Password Modal -->
<div id="changePasswordModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
<div class="bg-white rounded-lg p-6 w-full max-w-md">
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-bold">Changer le mot de passe</h2>
<button id="closeChangePasswordModal" class="text-gray-500 hover:text-gray-700">
<i class="fas fa-times"></i>
</button>
</div>
<div class="mb-4 relative">
<label class="block text-gray-700 mb-2" for="currentPassword">Mot de passe actuel</label>
<input type="password" id="currentPassword" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
<button id="toggleCurrentPassword" class="absolute right-3 top-9 text-gray-500">
<i class="fas fa-eye"></i>
</button>
</div>
<div class="mb-4 relative">
<label class="block text-gray-700 mb-2" for="newPassword">Nouveau mot de passe</label>
<input type="password" id="newPassword" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
<button id="toggleNewPassword" class="absolute right-3 top-9 text-gray-500">
<i class="fas fa-eye"></i>
</button>
</div>
<div class="mb-4 relative">
<label class="block text-gray-700 mb-2" for="confirmNewPassword">Confirmer le nouveau mot de passe</label>
<input type="password" id="confirmNewPassword" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
<button id="toggleConfirmNewPassword" class="absolute right-3 top-9 text-gray-500">
<i class="fas fa-eye"></i>
</button>
</div>
<button id="submitPasswordChange" class="w-full bg-blue-600 text-white py-2 rounded-lg hover:bg-blue-700 transition">Changer</button>
</div>
</div>
<!-- Blocked Users Modal -->
<div id="blockedUsersModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
<div class="bg-white rounded-lg p-6 w-full max-w-md">
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-bold">Utilisateurs bloqués</h2>
<button id="closeBlockedUsersModal" class="text-gray-500 hover:text-gray-700">
<i class="fas fa-times"></i>
</button>
</div>
<div id="blockedUsersList" class="space-y-3 max-h-64 overflow-y-auto">
<!-- Blocked users will be listed here -->
</div>
<div class="mt-4 text-center text-gray-500" id="noBlockedUsers">
Aucun utilisateur bloqué
</div>
</div>
</div>
<!-- Audio Recorder -->
<div id="audioRecorder" class="fixed bottom-4 right-4 bg-white p-4 rounded-lg shadow-lg hidden">
<div class="flex items-center space-x-4">
<button id="stopRecording" class="text-red-600">
<i class="fas fa-stop-circle text-2xl"></i>
</button>
<div class="flex-1">
<div class="flex items-center">
<div id="recordingTimer" class="text-gray-700 mr-3">00:00</div>
<div id="recordingVisualizer" class="flex-1 h-4 bg-gray-200 rounded-full overflow-hidden">
<div class="h-full bg-blue-500 animate-pulse" style="width: 30%"></div>
</div>
</div>
</div>
</div>
</div>
<script>
// Main App State
const state = {
currentUser: null,
users: JSON.parse(localStorage.getItem('users')) || [],
posts: JSON.parse(localStorage.getItem('posts')) || [],
groups: JSON.parse(localStorage.getItem('groups')) || [],
blockedUsers: JSON.parse(localStorage.getItem('blockedUsers')) || {},
audioRecorder: null,
recordingStartTime: null,
recordingTimer: null
};
// DOM Elements
const authModal = document.getElementById('authModal');
const app = document.getElementById('app');
const loginForm = document.getElementById('loginForm');
const registerForm = document.getElementById('registerForm');
const forgotPasswordForm = document.getElementById('forgotPasswordForm');
const loginTab = document.getElementById('loginTab');
const registerTab = document.getElementById('registerTab');
const loginBtn = document.getElementById('loginBtn');
const registerBtn = document.getElementById('registerBtn');
const forgotPassword = document.getElementById('forgotPassword');
const resetPasswordBtn = document.getElementById('resetPasswordBtn');
const backToLogin = document.getElementById('backToLogin');
const closeAuthModal = document.getElementById('closeAuthModal');
const toggleLoginPassword = document.getElementById('toggleLoginPassword');
const toggleRegisterPassword = document.getElementById('toggleRegisterPassword');
const loginUsername = document.getElementById('loginUsername');
const loginPassword = document.getElementById('loginPassword');
const fullName = document.getElementById('fullName');
const birthDate = document.getElementById('birthDate');
const location = document.getElementById('location');
const username = document.getElementById('username');
const password = document.getElementById('password');
const forgotUsername = document.getElementById('forgotUsername');
const forgotEmail = document.getElementById('forgotEmail');
const userMenuButton = document.getElementById('userMenuButton');
const userDropdown = document.getElementById('userDropdown');
const logoutLink = document.getElementById('logoutLink');
const profileLink = document.getElementById('profileLink');
const settingsLink = document.getElementById('settingsLink');
const userNameDisplay = document.getElementById('userNameDisplay');
const userAvatar = document.getElementById('userAvatar');
const currentUserAvatar = document.getElementById('currentUserAvatar');
const postContent = document.getElementById('postContent');
const postSubmit = document.getElementById('postSubmit');
const postImage = document.getElementById('postImage');
const postVideo = document.getElementById('postVideo');
const postAudio = document.getElementById('postAudio');
const postPreview = document.getElementById('postPreview');
const postsContainer = document.getElementById('postsContainer');
const startRecording = document.getElementById('startRecording');
const audioRecorder = document.getElementById('audioRecorder');
const stopRecording = document.getElementById('stopRecording');
const recordingTimer = document.getElementById('recordingTimer');
const createGroupBtn = document.getElementById('createGroupBtn');
const createGroupModal = document.getElementById('createGroupModal');
const closeGroupModal = document.getElementById('closeGroupModal');
const groupName = document.getElementById('groupName');
const groupDescription = document.getElementById('groupDescription');
const groupImage = document.getElementById('groupImage');
const createGroupSubmit = document.getElementById('createGroupSubmit');
const groupsList = document.getElementById('groupsList');
const groupsLink = document.getElementById('groupsLink');
const contactsList = document.getElementById('contactsList');
const settingsModal = document.getElementById('settingsModal');
const closeSettingsModal = document.getElementById('closeSettingsModal');
const settingsAvatar = document.getElementById('settingsAvatar');
const settingsAvatarInput = document.getElementById('settingsAvatarInput');
const settingsFullName = document.getElementById('settingsFullName');
const settingsUsername = document.getElementById('settingsUsername');
const settingsLocation = document.getElementById('settingsLocation');
const saveSettings = document.getElementById('saveSettings');
const changePasswordBtn = document.getElementById('changePasswordBtn');
const changePasswordModal = document.getElementById('changePasswordModal');
const closeChangePasswordModal = document.getElementById('closeChangePasswordModal');
const currentPassword = document.getElementById('currentPassword');
const newPassword = document.getElementById('newPassword');
const confirmNewPassword = document.getElementById('confirmNewPassword');
const submitPasswordChange = document.getElementById('submitPasswordChange');
const toggleCurrentPassword = document.getElementById('toggleCurrentPassword');
const toggleNewPassword = document.getElementById('toggleNewPassword');
const toggleConfirmNewPassword = document.getElementById('toggleConfirmNewPassword');
const blockedUsersBtn = document.getElementById('blockedUsersBtn');
const blockedUsersModal = document.getElementById('blockedUsersModal');
const closeBlockedUsersModal = document.getElementById('closeBlockedUsersModal');
const blockedUsersList = document.getElementById('blockedUsersList');
const noBlockedUsers = document.getElementById('noBlockedUsers');
// Initialize the app
function init() {
// Check if user is already logged in
const loggedInUser = localStorage.getItem('currentUser');
if (loggedInUser) {
state.currentUser = JSON.parse(loggedInUser);
renderApp();
} else {
authModal.classList.remove('hidden');
}
// Set up event listeners
setupEventListeners();
}
// Set up all event listeners
function setupEventListeners() {
// Auth modal
loginTab.addEventListener('click', () => {
loginTab.classList.add('border-blue-600', 'text-blue-600');
loginTab.classList.remove('text-gray-500');
registerTab.classList.add('text-gray-500');
registerTab.classList.remove('border-blue-600', 'text-blue-600');
loginForm.classList.remove('hidden');
registerForm.classList.add('hidden');
forgotPasswordForm.classList.add('hidden');
});
registerTab.addEventListener('click', () => {
registerTab.classList.add('border-blue-600', 'text-blue-600');
registerTab.classList.remove('text-gray-500');
loginTab.classList.add('text-gray-500');
loginTab.classList.remove('border-blue-600', 'text-blue-600');
registerForm.classList.remove('hidden');
loginForm.classList.add('hidden');
forgotPasswordForm.classList.add('hidden');
});
loginBtn.addEventListener('click', handleLogin);
registerBtn.addEventListener('click', handleRegister);
forgotPassword.addEventListener('click', () => {
forgotPasswordForm.classList.remove('hidden');
loginForm.classList.add('hidden');
registerForm.classList.add('hidden');
});
resetPasswordBtn.addEventListener('click', handleResetPassword);
backToLogin.addEventListener('click', () => {
forgotPasswordForm.classList.add('hidden');
loginForm.classList.remove('hidden');
});
closeAuthModal.addEventListener('click', () => {
authModal.classList.add('hidden');
});
toggleLoginPassword.addEventListener('click', () => {
togglePasswordVisibility(loginPassword, toggleLoginPassword);
});
toggleRegisterPassword.addEventListener('click', () => {
togglePasswordVisibility(password, toggleRegisterPassword);
});
// Main app
userMenuButton.addEventListener('click', () => {
userDropdown.classList.toggle('hidden');
});
logoutLink.addEventListener('click', handleLogout);
settingsLink.addEventListener('click', () => {
userDropdown.classList.add('hidden');
openSettingsModal();
});
profileLink.addEventListener('click', () => {
userDropdown.classList.add('hidden');
// In a real app, this would navigate to the profile page
alert('Page de profil');
});
// Post creation
postSubmit.addEventListener('click', createPost);
postImage.addEventListener('change', handleFileSelect);
postVideo.addEventListener('change', handleFileSelect);
postAudio.addEventListener('change', handleFileSelect);
startRecording.addEventListener('click', startAudioRecording);
stopRecording.addEventListener('click', stopAudioRecording);
// Groups
createGroupBtn.addEventListener('click', () => {
createGroupModal.classList.remove('hidden');
});
closeGroupModal.addEventListener('click', () => {
createGroupModal.classList.add('hidden');
});
createGroupSubmit.addEventListener('click', createGroup);
groupsLink.addEventListener('click', () => {
// In a real app, this would navigate to the groups page
alert('Page des groupes');
});
// Settings
closeSettingsModal.addEventListener('click', () => {
settingsModal.classList.add('hidden');
});
saveSettings.addEventListener('click', saveUserSettings);
settingsAvatarInput.addEventListener('change', handleAvatarChange);
changePasswordBtn.addEventListener('click', () => {
settingsModal.classList.add('hidden');
changePasswordModal.classList.remove('hidden');
});
closeChangePasswordModal.addEventListener('click', () => {
changePasswordModal.classList.add('hidden');
settingsModal.classList.remove('hidden');
});
submitPasswordChange.addEventListener('click', changePassword);
toggleCurrentPassword.addEventListener('click', () => {
togglePasswordVisibility(currentPassword, toggleCurrentPassword);
});
toggleNewPassword.addEventListener('click', () => {
togglePasswordVisibility(newPassword, toggleNewPassword);
});
toggleConfirmNewPassword.addEventListener('click', () => {
togglePasswordVisibility(confirmNewPassword, toggleConfirmNewPassword);
});
// Blocked users
blockedUsersBtn.addEventListener('click', () => {
settingsModal.classList.add('hidden');
blockedUsersModal.classList.remove('hidden');
renderBlockedUsers();
});
closeBlockedUsersModal.addEventListener('click', () => {
blockedUsersModal.classList.add('hidden');
settingsModal.classList.remove('hidden');
});
// Click outside dropdowns to close them
document.addEventListener('click', (e) => {
if (!userMenuButton.contains(e.target) && !userDropdown.contains(e.target)) {
userDropdown.classList.add('hidden');
}
});
}
// Toggle password visibility
function togglePasswordVisibility(passwordField, toggleButton) {
if (passwordField.type === 'password') {
passwordField.type = 'text';
toggleButton.innerHTML = '<i class="fas fa-eye-slash"></i>';
} else {
passwordField.type = 'password';
toggleButton.innerHTML = '<i class="fas fa-eye"></i>';
}
}
// Handle login
function handleLogin() {
const username = loginUsername.value.trim();
const password = loginPassword.value.trim();
if (!username || !password) {
alert('Veuillez remplir tous les champs');
return;
}
const user = state.users.find(u =>
(u.username === username || u.fullName === username) && u.password === password
);
if (user) {
state.currentUser = user;
localStorage.setItem('currentUser', JSON.stringify(user));
authModal.classList.add('hidden');
renderApp();
} else {
alert('Nom d\'utilisateur ou mot de passe incorrect');
}
}
// Handle registration
function handleRegister() {
const fullNameValue = fullName.value.trim();
const birthDateValue = birthDate.value;
const locationValue = location.value.trim();
const usernameValue = username.value.trim();
const passwordValue = password.value.trim();
if (!fullNameValue || !birthDateValue || !locationValue || !passwordValue) {
alert('Veuillez remplir tous les champs obligatoires');
return;
}
// Check if username is already taken
if (usernameValue && state.users.some(u => u.username === usernameValue)) {
alert('Ce nom d\'utilisateur est déjà pris');
return;
}
const newUser = {
id: Date.now().toString(),
fullName: fullNameValue,
birthDate: birthDateValue,
location: locationValue,
username: usernameValue || fullNameValue.toLowerCase().replace(/\s+/g, ''),
password: passwordValue,
avatar: `https://ui-avatars.com/api/?name=${encodeURIComponent(fullNameValue)}&background=random`,
joinedDate: new Date().toISOString()
};
state.users.push(newUser);
localStorage.setItem('users', JSON.stringify(state.users));
state.currentUser = newUser;
localStorage.setItem('currentUser', JSON.stringify(newUser));
authModal.classList.add('hidden');
renderApp();
alert('Inscription réussie ! Bienvenue sur SBI BOOK');
}
// Handle password reset
function handleResetPassword() {
const usernameValue = forgotUsername.value.trim();
const emailValue = forgotEmail.value.trim();
if (!usernameValue || !emailValue) {
alert('Veuillez remplir tous les champs');
return;
}
const user = state.users.find(u =>
(u.username === usernameValue || u.fullName === usernameValue) &&
u.email === emailValue
);
if (user) {
// In a real app, we would send a reset link to the email
alert('Un lien de réinitialisation a été envoyé à votre email');
forgotPasswordForm.classList.add('hidden');
loginForm.classList.remove('hidden');
} else {
alert('Aucun compte trouvé avec ces informations');
}
}
// Handle logout
function handleLogout() {
state.currentUser = null;
localStorage.removeItem('currentUser');
app.classList.add('hidden');
authModal.classList.remove('hidden');
// Reset forms
loginUsername.value = '';
loginPassword.value = '';
fullName.value = '';
birthDate.value = '';
location.value = '';
username.value = '';
password.value = '';
// Show login tab
loginTab.click();
}
// Render the main app
function renderApp() {
app.classList.remove('hidden');
// Set user info
userNameDisplay.textContent = state.currentUser.fullName;
userAvatar.src = state.currentUser.avatar;
currentUserAvatar.src = state.currentUser.avatar;
// Render posts
renderPosts();
// Render groups
renderGroups();
// Render contacts (non-blocked users)
renderContacts();
}
// Render posts
function renderPosts() {
postsContainer.innerHTML = '';
// Filter posts to exclude those from blocked users
const visiblePosts = state.posts.filter(post => {
return !state.blockedUsers[state.currentUser.id]?.includes(post.userId);
});
if (visiblePosts.length === 0) {
postsContainer.innerHTML = `
<div class="bg-white rounded-lg shadow p-6 text-center">
<p class="text-gray-500">Aucune publication à afficher. Soyez le premier à publier !</p>
</div>
`;
return;
}
visiblePosts.forEach(post => {
const postUser = state.users.find(u => u.id === post.userId) || {
fullName: 'Utilisateur inconnu',
avatar: 'https://ui-avatars.com/api/?name=Unknown&background=random'
};
const postElement = document.createElement('div');
postElement.className = 'bg-white rounded-lg shadow p-4';
postElement.dataset.postId = post.id;
let mediaElement = '';
if (post.media) {
if (post.media.type.startsWith('image')) {
mediaElement = `
<div class="mt-3">
<img src="${post.media.url}" alt="Post image" class="w-full rounded-lg">
</div>
`;
} else if (post.media.type.startsWith('video')) {
mediaElement = `
<div class="mt-3">
<video controls class="w-full rounded-lg">
<source src="${post.media.url}" type="${post.media.type}">
Votre navigateur ne supporte pas les vidéos.
</video>
</div>
`;
} else if (post.media.type.startsWith('audio')) {
mediaElement = `
<div class="mt-3">
<audio controls class="w-full">
<source src="${post.media.url}" type="${post.media.type}">
Votre navigateur ne supporte pas les audios.
</audio>
</div>
`;
}
}
let groupInfo = '';
if (post.groupId) {
const group = state.groups.find(g => g.id === post.groupId);
if (group) {
groupInfo = `
<div class="text-sm text-gray-500 mb-2">
<i class="fas fa-users mr-1"></i> Publié dans ${group.name}
</div>
`;
}
}
const isLiked = post.likes?.includes(state.currentUser.id) || false;
postElement.innerHTML = `
<div class="flex items-start space-x-3">
<div class="w-10 h-10 rounded-full bg-gray-300 overflow-hidden relative">
<img src="${postUser.avatar}" alt="Profile" class="w-full h-full object-cover">
<div class="online-status"></div>
</div>
<div class="flex-1">
<div class="flex justify-between items-start">
<div>
<h3 class="font-medium">${postUser.fullName}</h3>
<p class="text-xs text-gray-500">${formatDate(post.createdAt)}</p>
</div>
<div class="relative">
<button class="post-options text-gray-500 hover:text-gray-700">
<i class="fas fa-ellipsis-h"></i>
</button>
<div class="post-options-menu hidden absolute right-0 mt-1 w-40 bg-white rounded-md shadow-lg py-1 z-10">
${post.userId === state.currentUser.id ? `
<a href="#" class="block px-4 py-2 text-gray-800 hover:bg-gray-100 delete-post">Supprimer</a>
` : `
<a href="#" class="block px-4 py-2 text-gray-800 hover:bg-gray-100 block-user">Bloquer cet utilisateur</a>
`}
</div>
</div>
</div>
${groupInfo}
<p class="mt-2">${post.content}</p>
${mediaElement}
<div class="mt-3 flex justify-between text-gray-500 border-t pt-3">
<button class="like-btn flex items-center space-x-1 ${isLiked ? 'text-blue-600' : ''}">
<i class="fas fa-thumbs-up"></i>
<span>${post.likes?.length || 0}</span>
</button>
<button class="comment-btn flex items-center space-x-1">
<i class="fas fa-comment"></i>
<span>${post.comments?.length || 0}</span>
</button>
<button class="share-btn flex items-center space-x-1">
<i class="fas fa-share"></i>
<span>Partager</span>
</button>
${post.media ? `
<a href="${post.media.url}" download class="flex items-center space-x-1 text-green-600">
<i class="fas fa-download"></i>
<span>Télécharger</span>
</a>
` : ''}
</div>
</div>
</div>
<div class="post-comments mt-3 hidden">
<!-- Comments will be loaded here -->
</div>
<div class="mt-3 flex items-center">
<div class="w-8 h-8 rounded-full bg-gray-300 overflow-hidden mr-2">
<img src="${state.currentUser.avatar}" alt="Profile" class="w-full h-full object-cover">
</div>
<input type="text" placeholder="Écrire un commentaire..." class="comment-input flex-1 px-3 py-2 border rounded-full focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
`;
postsContainer.appendChild(postElement);
// Add event listeners for this post
const likeBtn = postElement.querySelector('.like-btn');
const commentBtn = postElement.querySelector('.comment-btn');
const shareBtn = postElement.querySelector('.share-btn');
const commentInput = postElement.querySelector('.comment-input');
const postOptionsBtn = postElement.querySelector('.post-options');
const postOptionsMenu = postElement.querySelector('.post-options-menu');
const deletePostBtn = postElement.querySelector('.delete-post');
const blockUserBtn = postElement.querySelector('.block-user');
likeBtn.addEventListener('click', () => toggleLike(post.id));
commentBtn.addEventListener('click', () => toggleComments(postElement, post.id));
shareBtn.addEventListener('click', () => sharePost(post.id));
commentInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter' && commentInput.value.trim()) {
addComment(post.id, commentInput.value.trim());
commentInput.value = '';
}
});
postOptionsBtn.addEventListener('click', () => {
postOptionsMenu.classList.toggle('hidden');
});
if (deletePostBtn) {
deletePostBtn.addEventListener('click', () => {
if (confirm('Supprimer cette publication ?')) {
deletePost(post.id);
}
});
}
if (blockUserBtn) {
blockUserBtn.addEventListener('click', () => {
if (confirm(`Bloquer ${postUser.fullName} ? Vous ne verrez plus ses publications.`)) {
blockUser(post.userId);
}
});
}
});
}
// Render groups
function renderGroups() {
groupsList.innerHTML = '';
if (state.groups.length === 0) {
groupsList.innerHTML = `
<div class="text-gray-500 text-sm">Aucun groupe disponible</div>
`;
return;
}
state.groups.forEach(group => {
const groupElement = document.createElement('div');
groupElement.className = 'flex items-center space-x-2 p-2 hover:bg-gray-100 rounded-lg cursor-pointer';
groupElement.dataset.groupId = group.id;
groupElement.innerHTML = `
<div class="w-10 h-10 rounded-full bg-gray-300 overflow-hidden">
<img src="${group.image || 'https://ui-avatars.com/api/?name=' + encodeURIComponent(group.name) + '&background=random'}" alt="${group.name}" class="w-full h-full object-cover">
</div>
<div>
<h4 class="font-medium">${group.name}</h4>
<p class="text-xs text-gray-500">${group.members.length} membres</p>
</div>
`;
groupElement.addEventListener('click', () => {
filterPostsByGroup(group.id);
});
groupsList.appendChild(groupElement);
});
}
// Render contacts
function renderContacts() {
contactsList.innerHTML = '';
// Filter out current user and blocked users
const contacts = state.users.filter(user =>
user.id !== state.currentUser.id &&
!state.blockedUsers[state.currentUser.id]?.includes(user.id)
);
if (contacts.length === 0) {
contactsList.innerHTML = `
<div class="text-gray-500 text-sm">Aucun contact disponible</div>
`;
return;
}
contacts.forEach(user => {
const contactElement = document.createElement('div');
contactElement.className = 'flex items-center space-x-2 p-2 hover:bg-gray-100 rounded-lg cursor-pointer';
contactElement.dataset.userId = user.id;
contactElement.innerHTML = `
<div class="w-10 h-10 rounded-full bg-gray-300 overflow-hidden relative">
<img src="${user.avatar}" alt="${user.fullName}" class="w-full h-full object-cover">
<div class="online-status"></div>
</div>
<div>
<h4 class="font-medium">${user.fullName}</h4>
<p class="text-xs text-gray-500">${user.location}</p>
</div>
`;
contactElement.addEventListener('click', () => {
// In a real app, this would open a chat or profile
alert(`Profil de ${user.fullName}`);
});
contactsList.appendChild(contactElement);
});
}
// Render comments for a post
function renderComments(postElement, postId) {
const post = state.posts.find(p => p.id === postId);
if (!post || !post.comments) return;
const commentsContainer = postElement.querySelector('.post-comments');
commentsContainer.innerHTML = '';
post.comments.forEach(comment => {
const commentUser = state.users.find(u => u.id === comment.userId) || {
fullName: 'Utilisateur inconnu',
avatar: 'https://ui-avatars.com/api/?name=Unknown&background=random'
};
const isLiked = comment.likes?.includes(state.currentUser.id) || false;
const isDisliked = comment.dislikes?.includes(state.currentUser.id) || false;
const commentElement = document.createElement('div');
commentElement.className = 'mb-3';
commentElement.dataset.commentId = comment.id;
commentElement.innerHTML = `
<div class="flex items-start space-x-2">
<div class="w-8 h-8 rounded-full bg-gray-300 overflow-hidden">
<img src="${commentUser.avatar}" alt="Profile" class="w-full h-full object-cover">
</div>
<div class="flex-1">
<div class="bg-gray-100 rounded-lg p-2">
<div class="flex justify-between items-start">
<div>
<h4 class="font-medium text-sm">${commentUser.fullName}</h4>
<p class="text-xs text-gray-500">${formatDate(comment.createdAt)}</p>
</div>
${comment.userId === state.currentUser.id ? `
<div class="flex space-x-1">
<button class="edit-comment text-gray-500 hover:text-gray-700 text-xs">
<i class="fas fa-edit"></i>
</button>
<button class="delete-comment text-gray-500 hover:text-gray-700 text-xs">
<i class="fas fa-trash"></i>
</button>
</div>
` : ''}
</div>
<p class="mt-1">${comment.content}</p>
</div>
<div class="flex items-center space-x-3 mt-1 ml-2 text-xs">
<button class="like-comment flex items-center space-x-1 ${isLiked ? 'text-blue-600' : ''}">
<i class="fas fa-thumbs-up"></i>
<span>${comment.likes?.length || 0}</span>
</button>
<button class="dislike-comment flex items-center space-x-1 ${isDisliked ? 'text-red-600' : ''}">
<i class="fas fa-thumbs-down"></i>
<span>${comment.dislikes?.length || 0}</span>
</button>
<button class="reply-comment text-gray-500 hover:text-gray-700">
Répondre
</button>
</div>
<div class="comment-reply-input mt-2 hidden">
<div class="flex items-center">
<div class="w-6 h-6 rounded-full bg-gray-300 overflow-hidden mr-2">
<img src="${state.currentUser.avatar}" alt="Profile" class="w-full h-full object-cover">
</div>
<input type="text" placeholder="Écrire une réponse..." class="flex-1 px-2 py-1 border rounded-full focus:outline-none focus:ring-2 focus:ring-blue-500 text-xs">
</div>
</div>
</div>
</div>
`;
commentsContainer.appendChild(commentElement);
// Add event listeners for this comment
const likeBtn = commentElement.querySelector('.like-comment');
const dislikeBtn = commentElement.querySelector('.dislike-comment');
const replyBtn = commentElement.querySelector('.reply-comment');
const editBtn = commentElement.querySelector('.edit-comment');
const deleteBtn = commentElement.querySelector('.delete-comment');
likeBtn.addEventListener('click', () => toggleCommentLike(postId, comment.id));
dislikeBtn.addEventListener('click', () => toggleCommentDislike(postId, comment.id));
replyBtn.addEventListener('click', () => toggleReplyInput(commentElement));
if (editBtn) {
editBtn.addEventListener('click', () => editComment(postId, comment.id));
}
if (deleteBtn) {
deleteBtn.addEventListener('click', () => {
if (confirm('Supprimer ce commentaire ?')) {
deleteComment(postId, comment.id);
}
});
}
// Render replies if any
if (comment.replies && comment.replies.length > 0) {
renderReplies(commentElement, postId, comment.id, comment.replies);
}
});
}
// Render replies for a comment
function renderReplies(commentElement, postId, commentId, replies) {
const repliesContainer = document.createElement('div');
repliesContainer.className = 'comment-reply mt-2';
replies.forEach(reply => {
const replyUser = state.users.find(u => u.id === reply.userId) || {
fullName: 'Utilisateur inconnu',
avatar: 'https://ui-avatars.com/api/?name=Unknown&background=random'
};
const replyElement = document.createElement('div');
replyElement.className = 'mb-2';
replyElement.dataset.replyId = reply.id;
replyElement.innerHTML = `
<div class="flex items-start space-x-2">
<div class="w-6 h-6 rounded-full bg-gray-300 overflow-hidden">
<img src="${replyUser.avatar}" alt="Profile" class="w-full h-full object-cover">
</div>
<div class="flex-1">
<div class="bg-gray-100 rounded-lg p-2">
<div class="flex justify-between items-start">
<div>
<h4 class="font-medium text-xs">${replyUser.fullName}</h4>
<p class="text-xs text-gray-500">${formatDate(reply.createdAt)}</p>
</div>
${reply.userId === state.currentUser.id ? `
<div class="flex space-x-1">
<button class="edit-reply text-gray-500 hover:text-gray-700 text-xs">
<i class="fas fa-edit"></i>
</button>
<button class="delete-reply text-gray-500 hover:text-gray-700 text-xs">
<i class="fas fa-trash"></i>
</button>
</div>
` : ''}
</div>
<p class="mt-1 text-xs">${reply.content}</p>
</div>
</div>
</div>
`;
repliesContainer.appendChild(replyElement);
// Add event listeners for this reply
const editBtn = replyElement.querySelector('.edit-reply');
const deleteBtn = replyElement.querySelector('.delete-reply');
if (editBtn) {
editBtn.addEventListener('click', () => editReply(postId, commentId, reply.id));
}
if (deleteBtn) {
deleteBtn.addEventListener('click', () => {
if (confirm('Supprimer cette réponse ?')) {
deleteReply(postId, commentId, reply.id);
}
});
}
});
commentElement.appendChild(repliesContainer);
}
// Render blocked users
function renderBlockedUsers() {
blockedUsersList.innerHTML = '';
const blockedUserIds = state.blockedUsers[state.currentUser.id] || [];
if (blockedUserIds.length === 0) {
noBlockedUsers.classList.remove('hidden');
return;
}
noBlockedUsers.classList.add('hidden');
blockedUserIds.forEach(userId => {
const user = state.users.find(u => u.id === userId);
if (!user) return;
const blockedUserElement = document.createElement('div');
blockedUserElement.className = 'flex items-center justify-between p-2 bg-gray-100 rounded-lg';
blockedUserElement.dataset.userId = userId;
blockedUserElement.innerHTML = `
<div class="flex items-center space-x-2">
<div class="w-8 h-8 rounded-full bg-gray-300 overflow-hidden">
<img src="${user.avatar}" alt="${user.fullName}" class="w-full h-full object-cover">
</div>
<div>
<h4 class="font-medium">${user.fullName}</h4>
<p class="text-xs text-gray-500">Bloqué</p>
</div>
</div>
<button class="unblock-user text-blue-600 hover:text-blue-800">
Débloquer
</button>
`;
blockedUsersList.appendChild(blockedUserElement);
// Add event listener for unblock button
const unblockBtn = blockedUserElement.querySelector('.unblock-user');
unblockBtn.addEventListener('click', () => {
unblockUser(userId);
});
});
}
// Create a new post
function createPost() {
const content = postContent.value.trim();
const mediaFile = postImage.files[0] || postVideo.files[0] || postAudio.files[0];
if (!content && !mediaFile) {
alert('Veuillez ajouter du texte ou un média');
return;
}
const newPost = {
id: Date.now().toString(),
userId: state.currentUser.id,
content: content,
createdAt: new Date().toISOString(),
likes: [],
comments: []
};
if (mediaFile) {
// In a real app, we would upload the file to a server
// Here we'll just create a local URL for demonstration
const fileUrl = URL.createObjectURL(mediaFile);
newPost.media = {
type: mediaFile.type,
url: fileUrl,
name: mediaFile.name,
size: mediaFile.size
};
// Check file size (20MB max)
if (mediaFile.size > 20 * 1024 * 1024) {
alert('Le fichier est trop volumineux (max 20MB)');
return;
}
}
state.posts.unshift(newPost);
localStorage.setItem('posts', JSON.stringify(state.posts));
// Reset form
postContent.value = '';
postPreview.classList.add('hidden');
postPreview.innerHTML = '';
postImage.value = '';
postVideo.value = '';
postAudio.value = '';
// Re-render posts
renderPosts();
}
// Handle file selection for posts
function handleFileSelect(e) {
const file = e.target.files[0];
if (!file) return;
// Clear other file inputs
if (e.target.id === 'postImage') {
postVideo.value = '';
postAudio.value = '';
} else if (e.target.id === 'postVideo') {
postImage.value = '';
postAudio.value = '';
} else if (e.target.id === 'postAudio') {
postImage.value = '';
postVideo.value = '';
}
// Check file size (20MB max)
if (file.size > 20 * 1024 * 1024) {
alert('Le fichier est trop volumineux (max 20MB)');
e.target.value = '';
return;
}
// Show preview
postPreview.classList.remove('hidden');
if (file.type.startsWith('image')) {
const reader = new FileReader();
reader.onload = (e) => {
postPreview.innerHTML = `
<img src="${e.target.result}" alt="Preview" class="w-full rounded-lg">
`;
};
reader.readAsDataURL(file);
} else if (file.type.startsWith('video')) {
const videoUrl = URL.createObjectURL(file);
postPreview.innerHTML = `
<video controls class="w-full rounded-lg">
<source src="${videoUrl}" type="${file.type}">
Votre navigateur ne supporte pas les vidéos.
</video>
`;
} else if (file.type.startsWith('audio')) {
const audioUrl = URL.createObjectURL(file);
postPreview.innerHTML = `
<audio controls class="w-full">
<source src="${audioUrl}" type="${file.type}">
Votre navigateur ne supporte pas les audios.
</audio>
`;
}
}
// Start audio recording
function startAudioRecording() {
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
navigator.mediaDevices.getUserMedia({ audio: true })
.then(stream => {
state.audioRecorder = new MediaRecorder(stream);
const audioChunks = [];
state.audioRecorder.ondataavailable = e => {
audioChunks.push(e.data);
};
state.audioRecorder.onstop = () => {
const audioBlob = new Blob(audioChunks, { type: 'audio/wav' });
// Create a preview of the audio recording
postPreview.classList.remove('hidden');
const audioUrl = URL.createObjectURL(audioBlob);
postPreview.innerHTML = `
<audio controls class="w-full">
<source src="${audioUrl}" type="audio/wav">
Votre navigateur ne supporte pas les audios.
</audio>
`;
// Create a file object to be used in the post
const audioFile = new File([audioBlob], 'recording.wav', { type: 'audio/wav' });
// Create a fake file input with the recording
const dataTransfer = new DataTransfer();
dataTransfer.items.add(audioFile);
postAudio.files = dataTransfer.files;
// Stop all tracks
stream.getTracks().forEach(track => track.stop());
};
// Start recording
state.audioRecorder.start();
state.recordingStartTime = Date.now();
// Update timer
state.recordingTimer = setInterval(() => {
const elapsed = Math.floor((Date.now() - state.recordingStartTime) / 1000);
const minutes = Math.floor(elapsed / 60).toString().padStart(2, '0');
const seconds = (elapsed % 60).toString().padStart(2, '0');
recordingTimer.textContent = `${minutes}:${seconds}`;
}, 1000);
// Show recorder UI
audioRecorder.classList.remove('hidden');
})
.catch(err => {
console.error('Error accessing microphone:', err);
alert('Impossible d\'accéder au microphone');
});
} else {
alert('L\'enregistrement audio n\'est pas supporté par votre navigateur');
}
}
// Stop audio recording
function stopAudioRecording() {
if (state.audioRecorder && state.audioRecorder.state !== 'inactive') {
state.audioRecorder.stop();
clearInterval(state.recordingTimer);
audioRecorder.classList.add('hidden');
recordingTimer.textContent = '00:00';
}
}
// Toggle like on a post
function toggleLike(postId) {
const post = state.posts.find(p => p.id === postId);
if (!post) return;
if (!post.likes) {
post.likes = [];
}
const likeIndex = post.likes.indexOf(state.currentUser.id);
if (likeIndex === -1) {
post.likes.push(state.currentUser.id);
} else {
post.likes.splice(likeIndex, 1);
}
localStorage.setItem('posts', JSON.stringify(state.posts));
renderPosts();
}
// Toggle comments visibility
function toggleComments(postElement, postId) {
const commentsContainer = postElement.querySelector('.post-comments');
commentsContainer.classList.toggle('hidden');
if (!commentsContainer.classList.contains('hidden') && commentsContainer.innerHTML === '') {
renderComments(postElement, postId);
}
}
// Share a post
function sharePost(postId) {
const post = state.posts.find(p => p.id === postId);
if (!post) return;
const sharedPost = {
id: Date.now().toString(),
userId: state.currentUser.id,
content: `Partagé depuis ${state.users.find(u => u.id === post.userId)?.fullName || 'un utilisateur'}: ${post.content}`,
createdAt: new Date().toISOString(),
sharedPostId: postId,
likes: [],
comments: []
};
if (post.media) {
sharedPost.media = {...post.media};
}
state.posts.unshift(sharedPost);
localStorage.setItem('posts', JSON.stringify(state.posts));
renderPosts();
alert('Publication partagée !');
}
// Add a comment to a post
function addComment(postId, content) {
const post = state.posts.find(p => p.id === postId);
if (!post) return;
if (!post.comments) {
post.comments = [];
}
const newComment = {
id: Date.now().toString(),
userId: state.currentUser.id,
content: content,
createdAt: new Date().toISOString(),
likes: [],
dislikes: [],
replies: []
};
post.comments.push(newComment);
localStorage.setItem('posts', JSON.stringify(state.posts));
// Find the post element and update its comments
const postElement = document.querySelector(`[data-post-id="${postId}"]`);
if (postElement) {
const commentsContainer = postElement.querySelector('.post-comments');
if (!commentsContainer.classList.contains('hidden')) {
renderComments(postElement, postId);
}
// Update comment count
const commentBtn = postElement.querySelector('.comment-btn');
if (commentBtn) {
const countSpan = commentBtn.querySelector('span');
countSpan.textContent = post.comments.length;
}
}
}
// Toggle comment like
function toggleCommentLike(postId, commentId) {
const post = state.posts.find(p => p.id === postId);
if (!post || !post.comments) return;
const comment = post.comments.find(c => c.id === commentId);
if (!comment) return;
if (!comment.likes) {
comment.likes = [];
}
const likeIndex = comment.likes.indexOf(state.currentUser.id);
if (likeIndex === -1) {
comment.likes.push(state.currentUser.id);
// Remove from dislikes if present
if (comment.dislikes) {
const dislikeIndex = comment.dislikes.indexOf(state.currentUser.id);
if (dislikeIndex !== -1) {
comment.dislikes.splice(dislikeIndex, 1);
}
}
} else {
comment.likes.splice(likeIndex, 1);
}
localStorage.setItem('posts', JSON.stringify(state.posts));
// Find the comment element and update its likes/dislikes
const commentElement = document.querySelector(`[data-comment-id="${commentId}"]`);
if (commentElement) {
const likeBtn = commentElement.querySelector('.like-comment');
const dislikeBtn = commentElement.querySelector('.dislike-comment');
if (likeBtn) {
likeBtn.classList.toggle('text-blue-600');
const countSpan = likeBtn.querySelector('span');
countSpan.textContent = comment.likes.length;
}
if (dislikeBtn) {
dislikeBtn.classList.remove('text-red-600');
const countSpan = dislikeBtn.querySelector('span');
countSpan.textContent = comment.dislikes?.length || 0;
}
}
}
// Toggle comment dislike
function toggleCommentDislike(postId, commentId) {
const post = state.posts.find(p => p.id === postId);
if (!post || !post.comments) return;
const comment = post.comments.find(c => c.id === commentId);
if (!comment) return;
if (!comment.dislikes) {
comment.dislikes = [];
}
const dislikeIndex = comment.dislikes.indexOf(state.currentUser.id);
if (dislikeIndex === -1) {
comment.dislikes.push(state.currentUser.id);
// Remove from likes if present
if (comment.likes) {
const likeIndex = comment.likes.indexOf(state.currentUser.id);
if (likeIndex !== -1) {
comment.likes.splice(likeIndex, 1);
}
}
} else {
comment.dislikes.splice(dislikeIndex, 1);
}
localStorage.setItem('posts', JSON.stringify(state.posts));
// Find the comment element and update its likes/dislikes
const commentElement = document.querySelector(`[data-comment-id="${commentId}"]`);
if (commentElement) {
const dislikeBtn = commentElement.querySelector('.dislike-comment');
const likeBtn = commentElement.querySelector('.like-comment');
if (dislikeBtn) {
dislikeBtn.classList.toggle('text-red-600');
const countSpan = dislikeBtn.querySelector('span');
countSpan.textContent = comment.dislikes.length;
}
if (likeBtn) {
likeBtn.classList.remove('text-blue-600');
const countSpan = likeBtn.querySelector('span');
countSpan.textContent = comment.likes?.length || 0;
}
}
}
// Toggle reply input for a comment
function toggleReplyInput(commentElement) {
const replyInput = commentElement.querySelector('.comment-reply-input');
replyInput.classList.toggle('hidden');
if (!replyInput.classList.contains('hidden')) {
const input = replyInput.querySelector('input');
input.focus();
input.addEventListener('keypress', (e) => {
if (e.key === 'Enter' && input.value.trim()) {
const postId = commentElement.closest('[data-post-id]').dataset.postId;
const commentId = commentElement.dataset.commentId;
addReply(postId, commentId, input.value.trim());
input.value = '';
replyInput.classList.add('hidden');
}
});
}
}
// Add a reply to a comment
function addReply(postId, commentId, content
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Hubertoo/sbi-book" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>