Файловый менеджер - Редактировать - /home/gqdcvggs/.trash/dashboard.php
Назад
<?php session_start(); require_once 'db.php'; require 'vendor/autoload.php'; if (!isset($_SESSION['user_id'])) { header('Location: login.php'); exit; } $db = new Database(); $conn = $db->connect(); $stmt = $conn->prepare("SELECT is_enabled FROM two_factor_auth WHERE user_id = ?"); $stmt->execute([$_SESSION['user_id']]); $twoFaStatus = $stmt->fetch(PDO::FETCH_COLUMN) ? true : false; function safe_json_decode($json, $default = []) { if ($json === null) return $default; $decoded = json_decode($json, true); return $decoded ?: $default; } function safe_datetime($datetime) { return $datetime ? date('M j, Y g:i A', strtotime($datetime)) : 'Not Available'; } $db = new Database(); $conn = $db->connect(); $errors = []; $success = ''; try { $stmt = $conn->prepare(" SELECT u.*, COALESCE(cw.`ip-connected`, '{}') as `ip-connected`, COALESCE(cw.`identified-screen`, '{}') as `identified-screen`, cw.`hours-of-connect`, cw.`date-of-connect` FROM utilisateurs u LEFT JOIN `connection-watchguard` cw ON u.id = cw.user_id WHERE u.id = ? ORDER BY cw.`date-of-connect` DESC LIMIT 1 "); $stmt->execute([$_SESSION['user_id']]); $user = $stmt->fetch(PDO::FETCH_ASSOC); if (!$user) { header('Location: logout.php'); exit; } // Récupération des connexions récentes $security_query = $conn->prepare(" SELECT cw.* FROM `connection-watchguard` cw WHERE cw.user_id = ? ORDER BY cw.`date-of-connect` DESC LIMIT 5 "); $security_query->execute([$_SESSION['user_id']]); $connections = $security_query->fetchAll(PDO::FETCH_ASSOC); } catch(PDOException $e) { error_log("Error fetching user data: " . $e->getMessage()); $errors[] = "Une erreur est survenue lors de la récupération de vos données."; } $subscriptions = [ 'academ' => $user['academ'] ?? false, 'ohmypanel' => $user['ohmypanel'] ?? false, 'something' => $user['something'] ?? false ]; if ($_SERVER['REQUEST_METHOD'] === 'POST') { if (isset($_POST['update_profile'])) { $username = trim(filter_var($_POST['username'], FILTER_SANITIZE_FULL_SPECIAL_CHARS)); $email = trim(filter_var($_POST['email'], FILTER_SANITIZE_EMAIL)); try { // Vérification email unique $check_email = $conn->prepare("SELECT id FROM utilisateurs WHERE email = ? AND id != ?"); $check_email->execute([$email, $_SESSION['user_id']]); if ($check_email->rowCount() > 0) { $errors[] = "This email is already taken by another user"; } else { $update_query = "UPDATE utilisateurs SET username = ?, email = ?"; $update_params = [$username, $email]; // Gestion photo de profil if (isset($_FILES['profile_picture']) && $_FILES['profile_picture']['error'] === 0) { $allowed = ['jpg', 'jpeg', 'png', 'webp']; $filename = $_FILES['profile_picture']['name']; $filetype = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); if (!in_array($filetype, $allowed)) { $errors[] = "Invalid file type. Allowed: JPG, JPEG, PNG, WEBP"; } else { $newName = time() . rand(1000, 9999) . '.' . $filetype; $uploadPath = $_SERVER['DOCUMENT_ROOT'] . '/../cdn.imators.com/uploads/'; if (!is_dir($uploadPath)) { mkdir($uploadPath, 0755, true); } if (move_uploaded_file($_FILES['profile_picture']['tmp_name'], $uploadPath . $newName)) { $cdnUrl = 'https://cdn.imators.com/uploads/' . $newName; $update_query .= ", `profile-picture` = ?"; $update_params[] = $cdnUrl; } else { $errors[] = "Failed to upload profile picture"; } } } $update_query .= " WHERE id = ?"; $update_params[] = $_SESSION['user_id']; if (empty($errors)) { $stmt = $conn->prepare($update_query); if ($stmt->execute($update_params)) { $success = "Profile updated successfully!"; $_SESSION['username'] = $username; // Refresh user data $stmt = $conn->prepare("SELECT * FROM utilisateurs WHERE id = ?"); $stmt->execute([$_SESSION['user_id']]); $user = $stmt->fetch(PDO::FETCH_ASSOC); } else { $errors[] = "Failed to update profile"; } } } } catch(PDOException $e) { error_log("Profile update error: " . $e->getMessage()); $errors[] = "An error occurred while updating your profile"; } } if (isset($_POST['change_password'])) { $currentPassword = $_POST['current_password']; $newPassword = $_POST['new_password']; $confirmPassword = $_POST['confirm_password']; if (!password_verify($currentPassword, $user['password'])) { $errors[] = "Current password is incorrect"; } elseif ($newPassword !== $confirmPassword) { $errors[] = "New passwords do not match"; } elseif (strlen($newPassword) < 8) { $errors[] = "New password must be at least 8 characters long"; } elseif (!preg_match("/[A-Z]/", $newPassword) || !preg_match("/[a-z]/", $newPassword) || !preg_match("/[0-9]/", $newPassword)) { $errors[] = "New password must contain uppercase, lowercase, and numbers"; } else { try { $hashedPassword = password_hash($newPassword, PASSWORD_DEFAULT); $stmt = $conn->prepare("UPDATE utilisateurs SET password = ? WHERE id = ?"); if ($stmt->execute([$hashedPassword, $_SESSION['user_id']])) { $success = "Password changed successfully!"; } else { $errors[] = "Failed to update password"; } } catch(PDOException $e) { error_log("Password update error: " . $e->getMessage()); $errors[] = "An error occurred while updating your password"; } } } } $stmt = $conn->prepare("SELECT passkey_enabled FROM utilisateurs WHERE id = ?"); $stmt->execute([$_SESSION['user_id']]); $passkey_enabled = $stmt->fetch(PDO::FETCH_COLUMN) ? true : false; ?> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Imators Auth.</title> <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600&display=swap" rel="stylesheet"> <script src="2fa-dashboard.js"></script> <script src="https://cdn.tailwindcss.com"></script> <style> body { font-family: 'Poppins', sans-serif; background: linear-gradient(135deg, #000000, #0a0a0a, #141414); background-attachment: fixed; } .glass-effect { backdrop-filter: blur(16px); background: rgba(255, 255, 255, 0.02); border: 1px solid rgba(255, 255, 255, 0.05); } .gradient-border { position: relative; border-radius: 1.25rem; background: linear-gradient(145deg, rgba(26, 26, 26, 0.8), rgba(45, 45, 45, 0.4)); padding: 1px; overflow: hidden; } .gradient-border::before { content: ''; position: absolute; inset: -1px; border-radius: 1.25rem; padding: 1px; background: linear-gradient(145deg, rgba(255, 255, 255, 0.12), rgba(255, 255, 255, 0.03)); -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); -webkit-mask-composite: xor; mask-composite: exclude; } .hover-scale { transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); } .hover-scale:hover { transform: translateY(-4px) scale(1.01); box-shadow: 0 20px 40px rgba(0, 0, 0, 0.2); } .profile-upload { position: relative; width: 100px; /* Taille de base réduite */ height: 100px; border-radius: 50%; overflow: hidden; cursor: pointer; transition: all 0.3s ease; margin: 0.5rem; } /* Ajout de media queries pour la responsivité */ @media (min-width: 640px) { .profile-upload { width: 120px; height: 120px; margin: 1rem; } } @media (min-width: 1024px) { .profile-upload { width: 140px; height: 140px; margin: 1.5rem; } } .profile-upload:hover::after { content: 'Change'; position: absolute; inset: 0; background: rgba(0, 0, 0, 0.7); display: flex; align-items: center; justify-content: center; color: white; font-size: 14px; } .connection-card { background: linear-gradient(165deg, rgba(26, 26, 26, 0.9), rgba(42, 42, 42, 0.5)); transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); border-radius: 1rem; } .connection-card:hover { transform: translateY(-2px); background: linear-gradient(165deg, rgba(36, 36, 36, 0.95), rgba(52, 52, 52, 0.6)); } input, select, textarea { background: rgba(0, 0, 0, 0.3); border: 1px solid rgba(255, 255, 255, 0.1); } input:focus, select:focus, textarea:focus { border-color: rgba(255, 255, 255, 0.2); outline: none; } button { transition: all 0.2s ease; } button:hover { transform: translateY(-1px); } .success-animation { animation: fadeInUp 0.6s cubic-bezier(0.4, 0, 0.2, 1); } @keyframes fadeInUp { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } } .nav-blur { backdrop-filter: blur(20px); background: rgba(0, 0, 0, 0.5); border-bottom: 1px solid rgba(255, 255, 255, 0.05); } @keyframes ripple { 0% { box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.4); } 100% { box-shadow: 0 0 0 10px rgba(59, 130, 246, 0); } } .pulse { animation: ripple 1.5s infinite; } </style> </head> <body class="bg-black min-h-screen text-white"> <nav class="border-b border-white/5 nav-blur sticky top-0 z-50"> <div class="flex justify-between items-center max-w-6xl mx-auto px-4 sm:px-6 py-4"> <div class="flex items-center space-x-6"> <img src="https://cdn.imators.com/logo.png" alt="Logo" class="h-8 sm:h-10 hover-scale"> <div class="hidden md:flex space-x-4"> <a href="#profile" class="text-gray-300 hover:text-white transition-colors">Profile</a> <a href="#security" class="text-gray-300 hover:text-white transition-colors">Security</a> <a href="#support" class="text-gray-300 hover:text-white transition-colors">Support</a> </div> </div> <div class="flex items-center space-x-4 sm:space-x-6"> <div class="flex items-center space-x-3"> <p class="text-sm text-gray-300"></p> <div class="w-8 h-8 sm:w-10 sm:h-10 rounded-full overflow-hidden"> <img src="<?php echo $user['profile-picture'] ?? 'https://cdn.imators.com/default-avatar.png'; ?>" alt="Profile" class="w-full h-full object-cover"> </div> </div> <a href="logout.php" class="px-3 py-2 sm:px-4 sm:py-2 rounded-lg bg-white/5 hover:bg-white/10 text-sm text-gray-300 hover:text-white transition-all"> Logout </a> </div> </div> </nav> <?php include 'ads/content.php'; ?> <main class="max-w-6xl mx-auto px-4 sm:px-6 py-8 sm:py-12"> <?php if ($success): ?> <div class="bg-green-500/10 border border-green-500/20 text-green-400 p-4 rounded-lg mb-8 success-animation"> <?php echo htmlspecialchars($success); ?> </div> <?php endif; ?> <?php if (!empty($errors)): ?> <div class="bg-red-500/10 border border-red-500/20 text-red-400 p-4 rounded-lg mb-8 success-animation"> <ul class="list-disc pl-4"> <?php foreach ($errors as $error): ?> <li><?php echo htmlspecialchars($error); ?></li> <?php endforeach; ?> </ul> </div> <?php endif; ?> <div class="mb-6 bg-transparent p-4 rounded-lg"> <h1 class="text-2xl sm:text-3xl font-semibold text-white"> Welcome, <span class="text-gradient"><?php echo htmlspecialchars($_SESSION['username']); ?></span> </h1> <p class="text-gray-400 mt-2">Manage your Imators account and preferences</p> </div> <div class="grid grid-cols-1 lg:grid-cols-3 gap-6"> <div class="lg:col-span-2 space-y-6"> <div id="profile" class="gradient-border hover-scale"> <div class="glass-effect rounded-lg p-4 sm:p-6 md:p-8"> <div class="flex flex-col sm:flex-row items-center sm:items-start gap-6 mb-8"> <div class="order-2 sm:order-1 text-center sm:text-left"> <h2 class="text-xl sm:text-2xl font-semibold flex items-center justify-center sm:justify-start"> <svg class="w-6 h-6 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" /> </svg> Profile Settings </h2> <p class="text-sm font-light mt-2 text-gray-400">Manage your account information</p> </div> <!-- Section Photo de profil --> <div class="order-1 sm:order-2 flex-shrink-0"> <div class="relative"> <label class="profile-upload block"> <input type="file" accept="image/*" class="hidden" name="profile_picture" id="profilePictureInput"> <img src="<?php echo $user['profile-picture'] ?? 'https://cdn.imators.com/default-avatar.png'; ?>" alt="Profile" class="w-full h-full object-cover" id="profilePreview"> </label> <?php if ($user['profile-picture']): ?> <div class="absolute -bottom-2 left-1/2 transform -translate-x-1/2 flex space-x-2"> <form method="POST" class="inline-block" id="removeProfileForm"> <input type="hidden" name="remove_profile_picture" value="1"> <button type="button" onclick="confirmRemoveProfile()" class="bg-red-500/20 hover:bg-red-500/30 text-red-400 p-1 rounded-full transition-all"> <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" /> </svg> </button> </form> </div> <?php endif; ?> <!-- Indicateur de chargement --> <div id="uploadStatus" class="absolute inset-0 bg-black/75 items-center justify-center hidden rounded-full"> <div class="text-center"> <svg class="animate-spin h-8 w-8 mx-auto text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"> <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle> <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path> </svg> <p class="mt-2 text-xs text-white">Uploading...</p> </div> </div> </div> </div> </div> <!-- Formulaire de profil --> <form method="POST" enctype="multipart/form-data" class="space-y-6" id="profileForm"> <div class="grid grid-cols-1 md:grid-cols-2 gap-6"> <div> <label class="block text-sm font-medium mb-2 text-gray-300">Username</label> <input type="text" name="username" required value="<?php echo htmlspecialchars($user['username']); ?>" placeholder="<?php echo htmlspecialchars($user['username']); ?>" class="w-full px-4 py-3 rounded-lg bg-black/50 border border-white/10 focus:border-white/30 focus:outline-none input-animated"> </div> <div> <label class="block text-sm font-medium mb-2 text-gray-300">Email</label> <input type="email" name="email" required value="<?php echo htmlspecialchars($user['email']); ?>" class="w-full px-4 py-3 rounded-lg bg-black/50 border border-white/10 focus:border-white/30 focus:outline-none input-animated"> </div> </div> <input type="hidden" name="update_profile" value="1"> <button type="submit" class="w-full bg-white text-black py-3 px-6 rounded-lg font-medium hover:bg-gray-100 transition-all hover-scale"> Update Profile </button> </form> </div> </div> <!-- Modal de confirmation pour la suppression de photo --> <div id="confirmationModal" class="fixed inset-0 bg-black/50 items-center justify-center hidden z-50"> <div class="bg-gray-900 p-6 rounded-lg max-w-sm mx-4"> <h3 class="text-lg font-medium mb-4">Confirm deletion</h3> <p class="text-gray-400 mb-6">Are you sure you want to delete your profile picture? This action is irreversible.</p> <div class="flex justify-end space-x-4"> <button onclick="closeConfirmationModal()" class="px-4 py-2 text-gray-400 hover:text-white transition-colors"> Cancel </button> <button onclick="removeProfile()" class="px-4 py-2 bg-red-500/20 text-red-400 rounded-lg hover:bg-red-500/30 transition-all"> Yes </button> </div> </div> </div> <div id="security" class="gradient-border hover-scale"> <div class="glass-effect rounded-lg p-6 sm:p-8"> <h2 class="text-xl sm:text-2xl font-semibold mb-6 flex items-center"> <svg class="w-6 h-6 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" /> </svg> Security & Connections </h2> <div class="space-y-6"> <div class="bg-black/30 rounded-lg p-4"> <div class="flex items-center justify-between"> <div> <h3 class="text-sm font-medium">Current Session</h3> <p class="text-xs text-gray-400 mt-1"> <?php $location = safe_json_decode($user['ip-connected'], [])['location'] ?? 'Unknown'; echo htmlspecialchars($location); ?> </p> </div> <div class="flex items-center"> <div class="w-2 h-2 bg-green-500 rounded-full pulse"></div> <span class="ml-2 text-xs text-gray-400">Active now</span> </div> </div> </div> <div class="space-y-4"> <h3 class="text-sm font-medium">Recent Connections</h3> <?php foreach ($connections as $connection): ?> <?php $deviceInfo = safe_json_decode($connection['identified-screen']); $ipInfo = safe_json_decode($connection['ip-connected']); ?> <div class="connection-card p-4"> <div class="flex justify-between items-start"> <div> <p class="text-sm"><?php echo htmlspecialchars($deviceInfo['device'] ?? 'Unknown Device'); ?></p> <p class="text-xs text-gray-400 mt-1"> <?php echo htmlspecialchars($ipInfo['location'] ?? 'Unknown Location'); ?> </p> </div> <p class="text-xs text-gray-500"> <?php echo safe_datetime($connection['date-of-connect']); ?> </p> </div> </div> <?php endforeach; ?> </div> <div class="bg-black/30 rounded-lg p-4 mb-6"> <div class="flex items-center justify-between"> <div class="flex items-center space-x-4"> <div class="w-10 h-10 rounded-full bg-white/5 flex items-center justify-center"> <svg class="w-5 h-5 text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z"/> </svg> </div> <div> <h3 class="text-sm font-medium">Passkey Authentification</h3> <p class="text-xs text-gray-400 mt-1">Account protection using biometric authentication.</p> </div> </div> <?php if ($passkey_enabled): ?> <div class="flex items-center"> <span class="flex items-center text-green-400 mr-4"> <svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/> </svg> <span class="text-sm">Activated</span> </span> <button onclick="openPasskeyRemoveModal()" class="px-4 py-2 bg-red-500/20 text-red-400 rounded-lg hover:bg-red-500/30 transition-colors flex items-center space-x-2"> <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/> </svg> <span>Deactivate</span> </button> </div> <?php else: ?> <div class="flex items-center"> <span class="flex items-center text-gray-400 mr-4"> <svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"/> </svg> <span class="text-sm">Deactivate</span> </span> <button onclick="openPasskeySetupModal()" class="px-4 py-2 bg-white/10 hover:bg-white/20 text-white rounded-lg transition-colors flex items-center space-x-2"> <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/> </svg> <span>Activated</span> </button> </div> <?php endif; ?> </div> </div> <script> document.getElementById('passkey-toggle').addEventListener('change', async function(e) { try { if (this.checked) { openPasskeySetupModal(); this.checked = false; } else { openPasskeyRemoveModal(); } } catch (error) { console.error('Error:', error); this.checked = !this.checked; } }); // Pour la désactivation async function removePasskey() { try { const response = await fetch('/auth/remove-passkey.php', { method: 'POST', headers: { 'Content-Type': 'application/json' } }); const result = await response.json(); if (result.success) { document.getElementById('passkey-toggle').checked = false; closePasskeyRemoveModal(); location.reload(); } else { throw new Error(result.error || 'Failed to disable Passkey'); } } catch (error) { showError(error.message); closePasskeyRemoveModal(); } } // Style pour le toggle const style = document.createElement('style'); style.textContent = ` .dot { transition: all 0.3s ease-in-out; } input:checked ~ .dot { background-color: rgb(74 222 128); } .block { transition: all 0.3s ease-in-out; } input:checked ~ .block { background-color: rgba(74, 222, 128, 0.2); } `; document.head.appendChild(style); </script> <div class="border-t border-white/5 pt-6"> <h3 class="text-sm font-medium mb-4">Change Password</h3> <form method="POST" class="space-y-4"> <div> <label class="block text-xs font-medium mb-2 text-gray-300">Current Password</label> <input type="password" name="current_password" required class="w-full px-4 py-3 rounded-lg bg-black/50 border border-white/10 focus:border-white/30 focus:outline-none input-animated"> </div> <div class="grid grid-cols-1 md:grid-cols-2 gap-4"> <div> <label class="block text-xs font-medium mb-2 text-gray-300">New Password</label> <input type="password" name="new_password" required class="w-full px-4 py-3 rounded-lg bg-black/50 border border-white/10 focus:border-white/30 focus:outline-none input-animated"> </div> <div> <label class="block text-xs font-medium mb-2 text-gray-300">Confirm Password</label> <input type="password" name="confirm_password" required class="w-full px-4 py-3 rounded-lg bg-black/50 border border-white/10 focus:border-white/30 focus:outline-none input-animated"> </div> </div> <input type="hidden" name="change_password" value="1"> <button type="submit" class="w-full bg-white/10 text-white py-3 px-6 rounded-lg font-medium hover:bg-white/20 transition-all"> Update Password </button> </form> </div> </div> </div> </div> <div id="support" class="gradient-border mb-8 hover-scale"> <div class="glass-effect rounded-lg p-6 sm:p-8"> <div class="flex justify-between items-center mb-6"> <div> <h2 class="text-xl sm:text-2xl font-semibold flex items-center"> <svg class="w-6 h-6 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 8h10M7 12h4m1 8l-4-4H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-3l-4 4z" /> </svg> Support Tickets </h2> <p class="text-sm font-light mt-2 text-gray-400">Get help from our team</p> </div> <a href="/create_ticket" class="px-4 py-2 bg-white text-black rounded-lg font-medium hover:bg-gray-100 transition-all hover-scale inline-flex items-center"> <svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" /> </svg> New Ticket </a> </div> <div class="overflow-x-auto"> <table class="w-full"> <thead> <tr class="text-left text-sm text-gray-400 border-b border-white/5"> <th class="pb-4 pr-4">ID</th> <th class="pb-4 pr-4">Subject</th> <th class="pb-4 pr-4">Status</th> <th class="pb-4 pr-4">Priority</th> <th class="pb-4 pr-4">Created</th> <th class="pb-4">Action</th> </tr> </thead> <tbody class="text-sm"> <?php try { $tickets_query = $conn->prepare(" SELECT * FROM support_tickets WHERE user_id = ? ORDER BY created_at DESC "); $tickets_query->execute([$_SESSION['user_id']]); $tickets = $tickets_query->fetchAll(PDO::FETCH_ASSOC); foreach ($tickets as $ticket): $status_colors = [ 'open' => ['bg' => 'bg-yellow-400/10', 'text' => 'text-yellow-400', 'border' => 'border-yellow-400/20'], 'in_progress' => ['bg' => 'bg-blue-400/10', 'text' => 'text-blue-400', 'border' => 'border-blue-400/20'], 'resolved' => ['bg' => 'bg-green-400/10', 'text' => 'text-green-400', 'border' => 'border-green-400/20'], 'closed' => ['bg' => 'bg-gray-400/10', 'text' => 'text-gray-400', 'border' => 'border-gray-400/20'] ][$ticket['status']] ?? ['bg' => 'bg-gray-400/10', 'text' => 'text-gray-400', 'border' => 'border-gray-400/20']; ?> <tr class="border-b border-white/5"> <td class="py-4 pr-4">#<?php echo $ticket['id']; ?></td> <td class="py-4 pr-4"><?php echo htmlspecialchars($ticket['subject']); ?></td> <td class="py-4 pr-4"> <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium border <?php echo $status_colors['bg'] . ' ' . $status_colors['text'] . ' ' . $status_colors['border']; ?>"> <?php echo str_replace('_', ' ', $ticket['status']); ?> </span> </td> <td class="py-4 pr-4 capitalize"><?php echo $ticket['priority']; ?></td> <td class="py-4 pr-4"><?php echo date('M j, Y', strtotime($ticket['created_at'])); ?></td> <td class="py-4"> <a href="view_ticket.php?id=<?php echo $ticket['id']; ?>" class="text-indigo-400 hover:text-indigo-300 transition-colors"> View Details </a> </td> </tr> <?php endforeach; } catch(PDOException $e) { error_log("Error fetching tickets: " . $e->getMessage()); } ?> </tbody> </table> <?php if (empty($tickets)): ?> <p class="text-gray-400 text-center py-8">No tickets found. Create one to get started!</p> <?php endif; ?> </div> </div> </div> </div> <div class="space-y-6"> <?php if($user['role']): ?> <div class="gradient-border hover-scale"> <div class="glass-effect rounded-lg p-6"> <div class="flex items-center space-x-4"> <div class="w-12 h-12 rounded-full bg-indigo-500/10 flex items-center justify-center"> <svg class="w-6 h-6 text-indigo-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4M7.835 4.697a3.42 3.42 0 001.946-.806 3.42 3.42 0 014.438 0 3.42 3.42 0 001.946.806 3.42 3.42 0 013.138 3.138 3.42 3.42 0 00.806 1.946 3.42 3.42 0 010 4.438 3.42 3.42 0 00-.806 1.946 3.42 3.42 0 01-3.138 3.138 3.42 3.42 0 00-1.946.806 3.42 3.42 0 01-4.438 0 3.42 3.42 0 00-1.946-.806 3.42 3.42 0 01-3.138-3.138 3.42 3.42 0 00-.806-1.946 3.42 3.42 0 010-4.438 3.42 3.42 0 00.806-1.946 3.42 3.42 0 013.138-3.138z" /> </svg> </div> <div> <h3 class="font-medium">Imators Staff Member</h3> <p class="text-sm text-gray-400"><?php echo htmlspecialchars($user['roleinimators']); ?> of the company</p> </div> </div> </div> </div> <?php endif; ?> <div class="gradient-border hover-scale"> <div class="glass-effect rounded-lg p-6"> <h3 class="font-medium mb-4">Your Active Subscriptions</h3> <div class="space-y-4"> <?php if($subscriptions['academ']): ?> <div class="subscription-card p-4 flex items-center space-x-4"> <img src="https://cdn.imators.com/Academ.png" alt="Academ" class="w-10 h-10"> <div> <p class="text-purple-200 text-sm">Academ</p> <p class="text-xs text-gray-400">Connected Account</p> </div> </div> <?php endif; ?> <?php if($subscriptions['ohmypanel']): ?> <div class="subscription-card p-4 flex items-center space-x-4 group"> <div class="relative"> <img src="https://cdn.imators.com/smile_not_found.png" alt="OhMyPanel" class="w-10 h-10"> <div class="absolute inset-0 bg-yellow-400/20 rounded-full filter blur-md opacity-0 group-hover:opacity-100 transition-opacity"></div> </div> <div> <p class="text-yellow-400 text-sm">OhMyPanel</p> <p class="text-xs text-gray-400">Active Subscription</p> </div> </div> <?php endif; ?> <?php if($subscriptions['something']): ?> <div class="subscription-card p-4 flex items-center space-x-4 group"> <div class="relative"> <img src="https://cdn.imators.com/logo.png" alt="Something" class="w-10 h-10"> <div class="absolute inset-0 bg-green-400/20 rounded-full filter blur-md opacity-0 group-hover:opacity-100 transition-opacity"></div> </div> <div> <p class="text-green-400 text-sm">Something</p> <p class="text-xs text-gray-400">Premium Access</p> </div> </div> <?php endif; ?> <?php if(!$subscriptions['academ'] && !$subscriptions['ohmypanel'] && !$subscriptions['something']): ?> <p class="text-gray-400 text-sm text-center py-4">No subscription or account connected to an Imators service</p> <?php endif; ?> </div> </div> </div> <div class="gradient-border hover-scale"> <div class="glass-effect rounded-lg p-6"> <h3 class="font-medium mb-4">Connection Info</h3> <div class="space-y-3"> <div class="flex justify-between text-sm"> <span class="text-gray-400">Location</span> <span><?php echo htmlspecialchars(safe_json_decode($user['ip-connected'], [])['location'] ?? 'Unknown'); ?></span> </div> <div class="flex justify-between text-sm"> <span class="text-gray-400">Device</span> <span><?php echo htmlspecialchars(safe_json_decode($user['identified-screen'], [])['device'] ?? 'Unknown'); ?></span> </div> <div class="flex justify-between text-sm"> <span class="text-gray-400">Last Login</span> <span><?php echo safe_datetime($user['date-of-connect']); ?></span> </div> </div> </div> </div> </div> </div> </main> <footer class="py-8 mt-12 border-t border-white/5"> <div class="max-w-6xl mx-auto px-4 sm:px-6"> <div class="flex flex-col items-center justify-center space-y-4"> <img src="https://cdn.imators.com/logo.png" alt="Imators Logo" class="h-8 opacity-50"> <p class="text-gray-400 text-sm text-center"> <?php echo date('Y'); ?> Imators. All rights reserved.<br> <span class="text-gray-500">Secure authentication system</span> </p> </div> </div> </footer> <script> // Gestion de la photo de profil document.getElementById('profilePictureInput').addEventListener('change', function(e) { if (e.target.files && e.target.files[0]) { const file = e.target.files[0]; // Validation du type de fichier const validTypes = ['image/jpeg', 'image/png', 'image/webp']; if (!validTypes.includes(file.type)) { showMessage('Veuillez sélectionner une image valide (JPG, PNG, ou WEBP)', 'error'); return; } // Validation de la taille if (file.size > 5 * 1024 * 1024) { showMessage('La taille du fichier doit être inférieure à 5MB', 'error'); return; } // Lecture et affichage de l'aperçu const reader = new FileReader(); reader.onload = function(e) { // Afficher l'aperçu document.getElementById('profilePreview').src = e.target.result; // Afficher l'indicateur de chargement document.getElementById('uploadStatus').style.display = 'flex'; // Soumettre le formulaire const formData = new FormData(); formData.append('profile_picture', file); formData.append('update_profile', '1'); fetch(window.location.href, { method: 'POST', body: formData }) .then(response => { if (!response.ok) throw new Error('Erreur réseau'); return response.text(); }) .then(() => { showMessage('Photo de profil mise à jour avec succès'); setTimeout(() => window.location.reload(), 1000); }) .catch(error => { console.error('Error:', error); showMessage('Erreur lors du téléchargement de l\'image', 'error'); }) .finally(() => { document.getElementById('uploadStatus').style.display = 'none'; }); }; reader.readAsDataURL(file); } }); // Gestion des formulaires document.querySelectorAll('form').forEach(form => { form.addEventListener('submit', function(e) { const submitButton = this.querySelector('button[type="submit"]'); if (submitButton?.disabled) { e.preventDefault(); return; } if (submitButton) { submitButton.disabled = true; submitButton.originalText = submitButton.innerHTML; submitButton.innerHTML = ` <span class="inline-flex items-center"> <svg class="animate-spin -ml-1 mr-3 h-5 w-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"> <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle> <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path> </svg> Traitement... </span>`; // Réactiver le bouton après 5 secondes si le formulaire n'est pas soumis setTimeout(() => { submitButton.disabled = false; submitButton.innerHTML = submitButton.originalText; }, 5000); } }); }); // Fonctions de gestion du modal de suppression function confirmRemoveProfile() { document.getElementById('confirmationModal').style.display = 'flex'; } function closeConfirmationModal() { document.getElementById('confirmationModal').style.display = 'none'; } function removeProfile() { document.getElementById('removeProfileForm').submit(); } // Gestion des messages flash function showMessage(message, type = 'success') { const flashContainer = document.createElement('div'); flashContainer.className = `fixed top-4 right-4 p-4 rounded-lg z-50 transform transition-all duration-300 opacity-0 translate-y-[-20px] ${ type === 'success' ? 'bg-green-500/10 border border-green-500/20 text-green-400' : 'bg-red-500/10 border border-red-500/20 text-red-400' }`; flashContainer.textContent = message; document.body.appendChild(flashContainer); // Animation d'entrée setTimeout(() => { flashContainer.style.opacity = '1'; flashContainer.style.transform = 'translateY(0)'; }, 100); // Animation de sortie setTimeout(() => { flashContainer.style.opacity = '0'; flashContainer.style.transform = 'translateY(-20px)'; setTimeout(() => flashContainer.remove(), 300); }, 5000); } // Gestion des clics en dehors du modal et touche Escape document.addEventListener('DOMContentLoaded', function() { const modal = document.getElementById('confirmationModal'); if (modal) { // Fermer le modal en cliquant en dehors modal.addEventListener('click', function(e) { if (e.target === modal) { closeConfirmationModal(); } }); // Fermer avec Escape document.addEventListener('keydown', function(e) { if (e.key === 'Escape' && modal.style.display === 'flex') { closeConfirmationModal(); } }); } // Animation des messages flash existants const messages = document.querySelectorAll('.success-animation, .error-animation'); messages.forEach(message => { message.style.opacity = '0'; message.style.transform = 'translateY(-20px)'; setTimeout(() => { message.style.opacity = '1'; message.style.transform = 'translateY(0)'; }, 100); setTimeout(() => { message.style.opacity = '0'; message.style.transform = 'translateY(-20px)'; setTimeout(() => message.remove(), 300); }, 5000); }); }); // Animation des messages de succès/erreur const messages = document.querySelectorAll('.success-animation, .error-animation'); messages.forEach(message => { message.style.opacity = '0'; setTimeout(() => { message.style.opacity = '1'; }, 100); // Auto-hide après 5 secondes setTimeout(() => { message.style.opacity = '0'; setTimeout(() => message.remove(), 300); }, 5000); }); </script> <div id="ticketModal" class="fixed inset-0 z-50 hidden overflow-y-auto"> <!-- Fond semi-transparent --> <div class="fixed inset-0 bg-black bg-opacity-50 transition-opacity backdrop-blur-sm"></div> <!-- Modal content --> <div class="flex min-h-screen items-center justify-center p-4"> <div class="gradient-border relative w-full max-w-2xl"> <div class="glass-effect rounded-xl p-6 sm:p-8"> <!-- Header --> <div class="flex items-center justify-between mb-6"> <h3 class="text-xl font-semibold text-white">Create New Support Ticket</h3> <button onclick="closeTicketModal()" class="text-gray-400 hover:text-white transition-colors"> <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" /> </svg> </button> </div> <!-- Form --> <form action="create_ticket.php" method="POST" class="space-y-6" id="ticketForm"> <!-- Subject --> <div> <label class="block text-sm font-medium text-gray-300 mb-2">Subject</label> <input type="text" name="subject" required class="w-full px-4 py-3 rounded-lg bg-black/50 border border-white/10 focus:border-white/30 focus:outline-none text-white placeholder-gray-500" placeholder="Brief description of your issue"> </div> <!-- Priority --> <div> <label class="block text-sm font-medium text-gray-300 mb-2">Priority</label> <select name="priority" required class="w-full px-4 py-3 rounded-lg bg-black/50 border border-white/10 focus:border-white/30 focus:outline-none text-white"> <option value="low">Low</option> <option value="medium">Medium</option> <option value="high">High</option> </select> </div> <!-- Description --> <div> <label class="block text-sm font-medium text-gray-300 mb-2">Description</label> <textarea name="description" required rows="6" class="w-full px-4 py-3 rounded-lg bg-black/50 border border-white/10 focus:border-white/30 focus:outline-none text-white placeholder-gray-500" placeholder="Please provide detailed information about your issue..."></textarea> </div> <!-- Submit button --> <div class="flex justify-end gap-4"> <button type="button" onclick="closeTicketModal()" class="px-4 py-2 rounded-lg border border-white/10 hover:bg-white/5 transition-colors text-gray-300"> Cancel </button> <button type="submit" id="submitTicket" class="px-6 py-2 bg-white text-black rounded-lg font-medium hover:bg-gray-100 transition-all hover-scale"> Create Ticket </button> </div> </form> </div> </div> </div> </div> <script> function openTicketModal(event) { event.preventDefault(); document.getElementById('ticketModal').classList.remove('hidden'); document.body.style.overflow = 'hidden'; } function closeTicketModal() { document.getElementById('ticketModal').classList.add('hidden'); document.body.style.overflow = 'auto'; document.getElementById('ticketForm').reset(); } // Fermer le modal quand on clique en dehors document.getElementById('ticketModal').addEventListener('click', function(event) { if (event.target === this) { closeTicketModal(); } }); // Fermer le modal avec la touche Escape document.addEventListener('keydown', function(event) { if (event.key === 'Escape') { closeTicketModal(); } }); // Animation de chargement lors de la soumission document.getElementById('ticketForm').addEventListener('submit', function(e) { const submitButton = this.querySelector('button[type="submit"]'); submitButton.disabled = true; submitButton.innerHTML = ` <svg class="animate-spin -ml-1 mr-3 h-5 w-5 inline-block" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"> <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle> <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path> </svg> Creating ticket... `; }); let currentStep = 1; let setupData = null; async function startSetup() { try { const response = await fetch('setup_2fa_ajax.php', { method: 'POST', headers: { 'Content-Type': 'application/json' } }); const data = await response.json(); if (data.success) { setupData = data; document.getElementById('qrcode').innerHTML = data.qrCode; document.getElementById('secretKey').textContent = data.secret; goToStep(2); } else { showError('Setup failed. Please try again.'); } } catch (error) { showError('Connection error. Please try again.'); } } function goToStep(step) { document.querySelectorAll('.step').forEach(el => { el.classList.add('hidden'); }); document.getElementById(`step${step}`).classList.remove('hidden'); currentStep = step; } async function verifySetup() { const code = document.getElementById('verificationCode').value; if (!code || code.length !== 6) { showError('Please enter a valid 6-digit code'); return; } try { const response = await fetch('verify_2fa_setup.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ code }) }); const result = await response.json(); if (result.success) { const codesHtml = result.backupCodes.map(code => `<div class="px-3 py-2 bg-black/20 rounded text-yellow-400 font-mono">${code}</div>` ).join(''); document.getElementById('backupCodes').innerHTML = codesHtml; goToStep(4); } else { showError(result.error || 'Invalid code'); } } catch (error) { showError('Verification failed'); } } function showError(message) { const error = document.getElementById('verificationError'); error.textContent = message; error.classList.remove('hidden'); } function downloadCodes() { const codes = Array.from(document.querySelectorAll('#backupCodes div')) .map(div => div.textContent) .join('\n'); const blob = new Blob([codes], { type: 'text/plain' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'backup-codes.txt'; a.click(); URL.revokeObjectURL(url); } function closeSetup2FAModal() { document.getElementById('setup2FAModal').classList.add('hidden'); resetSetup(); } function resetSetup() { currentStep = 1; setupData = null; document.querySelectorAll('.step').forEach(el => el.classList.add('hidden')); document.getElementById('step1').classList.remove('hidden'); document.getElementById('verificationCode').value = ''; document.getElementById('verificationError')?.classList.add('hidden'); } function finishSetup() { location.reload(); } </script> <!-- Setup Modal --> <div id="setup2FAModal" class="fixed inset-0 z-50 hidden" role="dialog" aria-modal="true"> <div class="fixed inset-0 bg-black/75 backdrop-blur-sm transition-opacity"></div> <div class="fixed inset-0 z-10 overflow-y-auto"> <div class="flex min-h-full items-center justify-center p-4"> <div class="gradient-border relative w-full max-w-2xl"> <div class="glass-effect rounded-2xl p-6 sm:p-8"> <div class="flex items-center justify-between mb-6"> <h2 class="text-2xl font-semibold text-white">2FA Setup</h2> <button onclick="closeSetup2FAModal()" class="text-gray-400 hover:text-white transition-colors"> <svg class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" /> </svg> </button> </div> <div id="setupSteps"> <!-- Step 1: Instructions --> <div class="step" id="step1"> <div class="mb-8"> <div class="p-6 border border-white/10 rounded-xl"> <h3 class="text-lg font-medium mb-4">Setup Process</h3> <ol class="space-y-4 text-gray-300"> <li>1. Install an authenticator app: <ul class="ml-6 mt-2 space-y-1 text-gray-400 text-sm"> <li>• Google Authenticator</li> <li>• Authy</li> <li>• Microsoft Authenticator</li> </ul> </li> <li>2. Scan the QR code with your app</li> <li>3. Enter the verification code</li> <li>4. Save your backup codes safely</li> </ol> </div> </div> <div class="flex justify-between"> <button onclick="closeSetup2FAModal()" class="px-4 py-2 rounded-lg border border-white/10 hover:bg-white/5"> Cancel </button> <button onclick="startSetup()" class="px-6 py-2 bg-white text-black rounded-lg font-medium hover:bg-gray-100"> Start Setup </button> </div> </div> <!-- Step 2: QR Code --> <div class="step hidden" id="step2"> <div class="text-center mb-8"> <div class="bg-white rounded-xl p-6 inline-block mb-4"> <div id="qrcode" class="w-64 h-64"> <div class="animate-pulse text-gray-400">Loading QR Code...</div> </div> </div> <div class="space-y-2"> <p class="text-gray-300">Can't scan the QR code?</p> <p class="text-sm text-gray-400">Enter this code manually in your app:</p> <code id="secretKey" class="px-3 py-1 bg-black/30 rounded text-yellow-400 text-lg"></code> </div> </div> <div class="flex justify-between"> <button onclick="goToStep(1)" class="px-4 py-2 rounded-lg border border-white/10 hover:bg-white/5"> Back </button> <button onclick="goToStep(3)" class="px-6 py-2 bg-white text-black rounded-lg font-medium hover:bg-gray-100"> Continue </button> </div> </div> <!-- Step 3: Verification --> <div class="step hidden" id="step3"> <div class="mb-8"> <h3 class="text-lg font-medium mb-2">Enter Verification Code</h3> <p class="text-gray-400 mb-6">Enter the 6-digit code from your authenticator app</p> <div class="max-w-xs mx-auto space-y-4"> <input type="text" id="verificationCode" class="w-full px-4 py-3 bg-black/30 border border-white/10 rounded-lg text-center text-2xl tracking-[0.5em] text-white" maxlength="6" placeholder="000000" autocomplete="off"> <div id="verificationError" class="hidden text-center text-red-400 text-sm"></div> </div> </div> <div class="flex justify-between"> <button onclick="goToStep(2)" class="px-4 py-2 rounded-lg border border-white/10 hover:bg-white/5"> Back </button> <button onclick="verifySetup()" class="px-6 py-2 bg-white text-black rounded-lg font-medium hover:bg-gray-100"> Verify </button> </div> </div> <!-- Step 4: Backup Codes --> <div class="step hidden" id="step4"> <div class="mb-8"> <div class="flex items-center space-x-3 mb-6"> <div class="w-10 h-10 rounded-full bg-green-500/10 flex items-center justify-center"> <svg class="w-6 h-6 text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" /> </svg> </div> <div> <h3 class="text-lg font-medium text-white">Setup Complete!</h3> <p class="text-gray-400">Save your backup codes now</p> </div> </div> <div class="bg-black/30 p-6 rounded-xl"> <div id="backupCodes" class="grid grid-cols-2 gap-4 text-center mb-4"></div> <div class="text-sm text-yellow-400 p-3 bg-yellow-400/10 rounded-lg mt-4"> Save these codes somewhere safe - they won't be shown again! </div> </div> </div> <div class="flex justify-between"> <button onclick="downloadCodes()" class="px-4 py-2 rounded-lg border border-white/10 hover:bg-white/5"> Download Codes </button> <button onclick="finishSetup()" class="px-6 py-2 bg-white text-black rounded-lg font-medium hover:bg-gray-100"> Finish </button> </div> </div> </div> </div> </div> </div> </div> </div> <!-- Disable Modal --> <div id="disable2FAModal" class="fixed inset-0 z-50 hidden" role="dialog" aria-modal="true"> <div class="fixed inset-0 bg-black/75 backdrop-blur-sm transition-opacity"></div> <div class="fixed inset-0 z-10 flex items-center justify-center p-4"> <div class="relative w-full max-w-md"> <div class="glass-effect rounded-xl p-6"> <h3 class="text-xl font-medium mb-4">Disable 2FA</h3> <p class="text-gray-400 mb-6">Enter a verification code to disable two-factor authentication</p> <input type="text" id="disableCode" class="w-full px-4 py-3 bg-black/30 border border-white/10 rounded-lg text-center text-2xl tracking-[0.5em]" maxlength="6" placeholder="000000" autocomplete="off"> <div id="disableError" class="mt-2 text-red-400 text-sm hidden"></div> <div class="flex justify-between mt-6"> <button onclick="closeDisable2FAModal()" class="px-4 py-2 rounded-lg border border-white/10 hover:bg-white/5"> Cancel </button> <button onclick="disableTwoFactor()" class="px-6 py-2 bg-red-500/20 text-red-400 rounded-lg hover:bg-red-500/30"> Disable 2FA </button> </div> </div> </div> </div> </div> <!-- Modal Configuration Passkey --> <div id="passkey-setup-modal" class="fixed inset-0 bg-black/75 backdrop-blur-sm hidden z-50"> <div class="relative min-h-screen flex items-center justify-center p-4"> <div class="gradient-border relative w-full max-w-md"> <div class="glass-effect rounded-xl p-6"> <div class="flex items-center justify-between mb-6"> <h3 class="text-xl font-medium">Passkey Configuration</h3> <button onclick="closePasskeySetupModal()" class="text-gray-400 hover:text-white"> <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/> </svg> </button> </div> <div id="setup-steps"> <!-- Étape 1: Introduction --> <div id="step-1" class="space-y-4"> <p class="text-gray-300"> Passkey lets you log in securely without a password, using biometrics or your device's PIN code. </p> <button onclick="startPasskeyRegistration()" class="w-full bg-white text-black py-3 px-4 rounded-lg font-medium hover:bg-gray-100 transition-all"> Start configuration </button> </div> <!-- Étape 2: Configuration en cours --> <div id="step-2" class="hidden space-y-4 text-center"> <div class="flex justify-center"> <div class="w-16 h-16 bg-blue-500/20 rounded-full flex items-center justify-center animate-pulse"> <svg class="w-8 h-8 text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 11c0 3.517-1.009 6.799-2.753 9.571m-3.44-2.04l.054-.09A13.916 13.916 0 008 11a4 4 0 118 0c0 1.017-.07 2.019-.203 3m-2.118 6.844A21.88 21.88 0 0015.171 17m3.839 1.132c.645-2.266.99-4.659.99-7.132A8 8 0 008 4.07M3 15.364c.64-1.319 1-2.8 1-4.364 0-1.457.39-2.823 1.07-4"/> </svg> </div> </div> <p class="text-gray-300">Follow the instructions on your device...</p> </div> </div> </div> </div> </div> </div> <!-- Modal Suppression Passkey --> <div id="passkey-remove-modal" class="fixed inset-0 bg-black/75 backdrop-blur-sm hidden z-50"> <div class="relative min-h-screen flex items-center justify-center p-4"> <div class="gradient-border relative w-full max-w-md"> <div class="glass-effect rounded-xl p-6"> <h3 class="text-xl font-medium mb-4">Disable Passkey</h3> <p class="text-gray-300 mb-6"> Are you sure you want to disable Passkey authentication? Doing so will reduce the security of your account. </p> <div class="flex justify-end space-x-4"> <button onclick="closePasskeyRemoveModal()" class="px-4 py-2 text-gray-400 hover:text-white transition-colors"> Cancel </button> <button onclick="removePasskey()" class="px-4 py-2 bg-red-500/20 text-red-400 rounded-lg hover:bg-red-500/30 transition-all"> Deactivate </button> </div> </div> </div> </div> </div> <script> // Gestionnaires des modals function openPasskeySetupModal() { document.getElementById('passkey-setup-modal').classList.remove('hidden'); document.getElementById('step-1').classList.remove('hidden'); document.getElementById('step-2').classList.add('hidden'); } function closePasskeySetupModal() { document.getElementById('passkey-setup-modal').classList.add('hidden'); } function openPasskeyRemoveModal() { document.getElementById('passkey-remove-modal').classList.remove('hidden'); } function closePasskeyRemoveModal() { document.getElementById('passkey-remove-modal').classList.add('hidden'); } // Fonctions de gestion Passkey async function startPasskeyRegistration() { try { document.getElementById('step-1').classList.add('hidden'); document.getElementById('step-2').classList.remove('hidden'); const response = await fetch('/auth/register-passkey', { method: 'POST', headers: { 'Content-Type': 'application/json' } }); const options = await response.json(); if (!options.success) throw new Error(options.error); const credential = await navigator.credentials.create({ publicKey: { challenge: base64ToArrayBuffer(options.challenge), rp: { name: 'Imators', id: window.location.hostname }, user: { id: base64ToArrayBuffer(options.userId), name: options.userEmail, displayName: options.userName }, pubKeyCredParams: [{ alg: -7, type: 'public-key' }], timeout: 60000, attestation: 'none' } }); const result = await fetch('/auth/complete-passkey-registration', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: credential.id, rawId: arrayBufferToBase64(credential.rawId), response: { clientDataJSON: arrayBufferToBase64(credential.response.clientDataJSON), attestationObject: arrayBufferToBase64(credential.response.attestationObject) } }) }); const verification = await result.json(); if (verification.success) { location.reload(); } else { throw new Error(verification.error); } } catch (error) { showError(error.message || 'La configuration a échoué'); } finally { closePasskeySetupModal(); } } async function removePasskey() { try { const response = await fetch('/auth/remove-passkey', { method: 'POST', headers: { 'Content-Type': 'application/json' } }); const result = await response.json(); if (result.success) { location.reload(); } else { throw new Error(result.error); } } catch (error) { showError(error.message || 'Échec de la désactivation'); } finally { closePasskeyRemoveModal(); } } // Utilitaires function base64ToArrayBuffer(base64) { const binaryString = window.atob(base64); const bytes = new Uint8Array(binaryString.length); for (let i = 0; i < binaryString.length; i++) { bytes[i] = binaryString.charCodeAt(i); } return bytes.buffer; } function arrayBufferToBase64(buffer) { const bytes = new Uint8Array(buffer); let binary = ''; for (let i = 0; i < bytes.byteLength; i++) { binary += String.fromCharCode(bytes[i]); } return window.btoa(binary); } </script> </body> </html>
| ver. 1.6 |
Github
|
.
| PHP 8.1.33 | Генерация страницы: 0 |
proxy
|
phpinfo
|
Настройка