Modern Homepage Design - Copy this Html, Tailwind Component to your project
<!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title data-i18n="profile">Profile Settings</title> <script src="js/theme-preloader.js"></script> <script src="https://cdn.tailwindcss.com"></script> <script> tailwind.config = { darkMode: 'class', theme: { extend: { colors: { coral: { 400: '#FF7F50', 500: '#FF6347', 600: '#FF4500', }, }, }, }, } </script> <link href="https://cdnjs.cloudflare.com/ajax/libs/flowbite/2.2.0/flowbite.min.css" rel="stylesheet" /> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"> <style> button:hover { opacity: 0.9; } </style> <link rel="stylesheet" href="styles/profilesettings.css"> </head> <body class="bg-gray-50 dark:bg-gray-900 transition-colors duration-200"> <div class="min-h-screen py-12 px-4 sm:px-6 lg:px-8"> <div class="max-w-3xl mx-auto bg-white dark:bg-gray-800 rounded-xl shadow-lg"> <div class="px-8 py-6"> <div class="flex items-center justify-between mb-6"> <button onclick="window.location.href='index.html'" class="flex items-center text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition-colors duration-150"> <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18" /> </svg> <span data-i18n="back">Back</span> </button> </div> <h1 class="text-3xl font-bold text-gray-900 dark:text-white mb-8" data-i18n="settings">Settings</h1> <form id="profileForm" class="space-y-6"> <div class="flex flex-col items-center space-y-4"> <div class="relative group"> <img id="avatar" src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80" alt="Profile" class="w-32 h-32 rounded-full object-cover border-4 border-white dark:border-gray-700 shadow-lg group-hover:opacity-75 transition duration-150"> <div class="absolute inset-0 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity duration-150"> <label for="avatar-upload" class="cursor-pointer"> <div class="bg-black bg-opacity-50 rounded-full p-2"> <svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 13a3 3 0 11-6 0 3 3 0 016 0z" /> </svg> </div> </label> </div> </div> <input type="file" id="avatar-upload" accept="image/*" class="hidden"> <div class="text-center"> <p class="text-sm text-gray-500 dark:text-gray-400" data-i18n="maxFileSize">Maximum file size: 5MB</p> <p class="text-sm text-gray-500 dark:text-gray-400" data-i18n="allowedFormats">Allowed formats: JPG, PNG</p> </div> </div> <div class="grid grid-cols-1 md:grid-cols-2 gap-6"> <div class="space-y-2"> <label for="firstName" class="block text-sm font-medium text-gray-700 dark:text-gray-300" data-i18n="firstName">First Name</label> <input type="text" id="firstName" data-i18n-placeholder="enterFirstName" class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-coral-500 focus:border-transparent bg-white dark:bg-gray-700 text-gray-900 dark:text-white transition duration-150"> </div> <div class="space-y-2"> <label for="lastName" class="block text-sm font-medium text-gray-700 dark:text-gray-300" data-i18n="lastName">Last Name</label> <input type="text" id="lastName" data-i18n-placeholder="enterLastName" class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-coral-500 focus:border-transparent bg-white dark:bg-gray-700 text-gray-900 dark:text-white transition duration-150"> </div> <div class="space-y-2"> <label for="middleName" class="block text-sm font-medium text-gray-700 dark:text-gray-300" data-i18n="middleName">Middle Name</label> <input type="text" id="middleName" data-i18n-placeholder="enterMiddleName" class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-coral-500 focus:border-transparent bg-white dark:bg-gray-700 text-gray-900 dark:text-white transition duration-150"> </div> <div class="space-y-2"> <label for="nickname" class="block text-sm font-medium text-gray-700 dark:text-gray-300" data-i18n="nickname">Nickname</label> <input type="text" id="nickname" data-i18n-placeholder="enterNickname" class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-coral-500 focus:border-transparent bg-white dark:bg-gray-700 text-gray-900 dark:text-white transition duration-150"> </div> <div class="space-y-2"> <label for="email" class="block text-sm font-medium text-gray-700 dark:text-gray-300" data-i18n="email">Email Address</label> <input type="email" id="email" data-i18n-placeholder="enterEmail" class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-coral-500 focus:border-transparent bg-white dark:bg-gray-700 text-gray-900 dark:text-white transition duration-150"> </div> <div class="space-y-2 md:col-span-2"> <label for="currentPassword" class="block text-sm font-medium text-gray-700 dark:text-gray-300" data-i18n="currentPassword">Current Password</label> <div class="relative"> <input type="password" id="currentPassword" data-i18n-placeholder="enterPassword" class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-coral-500 focus:border-transparent bg-white dark:bg-gray-700 text-gray-900 dark:text-white transition duration-150"> <button type="button" onclick="togglePassword('currentPassword')" class="absolute inset-y-0 right-0 pr-3 flex items-center"> <svg class="h-5 w-5 text-gray-400 cursor-pointer hover:text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" /> </svg> </button> </div> </div> <div class="space-y-2 md:col-span-2"> <label for="newPassword" class="block text-sm font-medium text-gray-700 dark:text-gray-300" data-i18n="newPassword">New Password</label> <div class="relative"> <input type="password" id="newPassword" data-i18n-placeholder="enterNewPassword" minlength="8" pattern="^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$" class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-coral-500 focus:border-transparent bg-white dark:bg-gray-700 text-gray-900 dark:text-white transition duration-150"> <button type="button" onclick="togglePassword('newPassword')" class="absolute inset-y-0 right-0 pr-3 flex items-center"> <svg class="h-5 w-5 text-gray-400 cursor-pointer hover:text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" /> </svg> </button> </div> <div id="password-requirements" class="mt-1 text-sm text-gray-500 dark:text-gray-400"> <p data-i18n="passwordRequirements">Password must contain at least 8 characters, including uppercase, lowercase, number and special character</p> </div> <div id="password-strength" class="mt-2"> <p class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" data-i18n="passwordStrength">Password Strength</p> <div class="h-2 bg-gray-200 rounded-full"> <div id="strength-bar" class="h-2 rounded-full transition-all duration-300"></div> </div> <p id="strength-text" class="text-sm text-gray-500 dark:text-gray-400 mt-1"></p> </div> </div> <div class="space-y-2 md:col-span-2"> <label for="confirmPassword" class="block text-sm font-medium text-gray-700 dark:text-gray-300" data-i18n="confirmPassword">Confirm Password</label> <div class="relative"> <input type="password" id="confirmPassword" data-i18n-placeholder="enterConfirmPassword" class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-coral-500 focus:border-transparent bg-white dark:bg-gray-700 text-gray-900 dark:text-white transition duration-150"> <button type="button" onclick="togglePassword('confirmPassword')" class="absolute inset-y-0 right-0 pr-3 flex items-center"> <svg class="h-5 w-5 text-gray-400 cursor-pointer hover:text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" /> </svg> </button> </div> </div> </div> <div class="flex justify-end space-x-4"> <button type="button" onclick="window.location.href='index.html'" class="px-6 py-2 border border-gray-300 dark:border-gray-600 rounded-lg text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-coral-500 transition duration-150" data-i18n="cancel">Cancel</button> <button type="submit" class="px-6 py-2 bg-coral-500 text-white rounded-lg hover:bg-coral-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-coral-500 transition duration-150" data-i18n="saveChanges">Save Changes</button> </div> </form> </div> </div> </div> <script src="https://cdnjs.cloudflare.com/ajax/libs/flowbite/2.2.0/flowbite.min.js"></script> <script src="js/translations.js"></script> <script src="js/i18n.js"></script> <script src="js/auth-interceptor.js"></script> <script src="js/auth.js"></script> <script src="js/theme-manager.js"></script> <script> let originalData = {}; // Initialize language and theme document.addEventListener('DOMContentLoaded', async function() { // Инициализация языка const currentLang = localStorage.getItem('language') || 'ru'; localStorage.setItem('language', currentLang); await i18n.init(); document.documentElement.lang = currentLang; // Initialize theme from localStorage or system preference if (localStorage.theme === 'dark' || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) { document.documentElement.classList.add('dark'); } else { document.documentElement.classList.remove('dark'); } // Listen for system theme changes if user hasn't set a preference window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => { if (!localStorage.theme) { setTheme(e.matches); } }); // Загрузка данных пользователя try { const response = await fetchWithAuth('http://127.0.0.1:8000/api/users/get_current_user/'); const userData = await response.json(); // Сохраняем оригинальные данные originalData = { first_name: userData.first_name || '', last_name: userData.last_name || '', middle_name: userData.middle_name || '', username: userData.username || '', email: userData.email || '', avatar: userData.avatar || '' }; // Заполняем форму document.getElementById('firstName').value = originalData.first_name; document.getElementById('lastName').value = originalData.last_name; document.getElementById('middleName').value = originalData.middle_name; document.getElementById('nickname').value = originalData.username; document.getElementById('email').value = originalData.email; if (originalData.avatar) { document.getElementById('avatar').src = originalData.avatar; } } catch (error) { console.error('Error fetching user data:', error); } }); // Toggle password fields visibility function togglePasswordFields() { const passwordFields = document.getElementById('passwordChangeFields'); const isHidden = passwordFields.classList.contains('hidden'); if (isHidden) { passwordFields.classList.remove('hidden'); passwordFields.classList.add('animate-fade-in'); } else { passwordFields.classList.add('hidden'); passwordFields.classList.remove('animate-fade-in'); // Reset password fields document.getElementById('currentPassword').value = ''; document.getElementById('newPassword').value = ''; document.getElementById('confirmPassword').value = ''; resetPasswordStrength(); } } // Check password strength function checkPasswordStrength(password) { const minLength = password.length >= 8; const hasUpperCase = /[A-Z]/.test(password); const hasLowerCase = /[a-z]/.test(password); const hasNumber = /\d/.test(password); const hasSpecialChar = /[!@#$%^&*(),.?":{}|<>]/.test(password); // Update requirement checks document.getElementById('minLengthCheck').textContent = minLength ? '✓' : '✗'; document.getElementById('minLengthCheck').className = minLength ? 'text-green-500' : 'text-red-500'; document.getElementById('upperCaseCheck').textContent = hasUpperCase ? '✓' : '✗'; document.getElementById('upperCaseCheck').className = hasUpperCase ? 'text-green-500' : 'text-red-500'; document.getElementById('lowerCaseCheck').textContent = hasLowerCase ? '✓' : '✗'; document.getElementById('lowerCaseCheck').className = hasLowerCase ? 'text-green-500' : 'text-red-500'; document.getElementById('numberCheck').textContent = hasNumber ? '✓' : '✗'; document.getElementById('numberCheck').className = hasNumber ? 'text-green-500' : 'text-red-500'; document.getElementById('specialCharCheck').textContent = hasSpecialChar ? '✓' : '✗'; document.getElementById('specialCharCheck').className = hasSpecialChar ? 'text-green-500' : 'text-red-500'; // Calculate strength let strength = 0; if (minLength) strength += 20; if (hasUpperCase) strength += 20; if (hasLowerCase) strength += 20; if (hasNumber) strength += 20; if (hasSpecialChar) strength += 20; // Update strength bar const strengthBar = document.getElementById('passwordStrengthBar'); const strengthText = document.getElementById('passwordStrengthText'); strengthBar.style.width = strength + '%'; if (strength <= 40) { strengthBar.className = 'h-2 rounded-full bg-red-500 transition-all duration-300'; strengthText.textContent = i18n.t('passwordWeak'); } else if (strength <= 80) { strengthBar.className = 'h-2 rounded-full bg-yellow-500 transition-all duration-300'; strengthText.textContent = i18n.t('passwordMedium'); } else { strengthBar.className = 'h-2 rounded-full bg-green-500 transition-all duration-300'; strengthText.textContent = i18n.t('passwordStrong'); } } function resetPasswordStrength() { document.getElementById('passwordStrengthBar').style.width = '0%'; document.getElementById('passwordStrengthText').textContent = ''; const checks = ['minLengthCheck', 'upperCaseCheck', 'lowerCaseCheck', 'numberCheck', 'specialCharCheck']; checks.forEach(check => { document.getElementById(check).textContent = '✗'; document.getElementById(check).className = 'text-red-500'; }); } // Check if passwords match function checkPasswordsMatch() { const newPassword = document.getElementById('newPassword').value; const confirmPassword = document.getElementById('confirmPassword').value; const errorElement = document.getElementById('passwordMatchError'); if (confirmPassword && newPassword !== confirmPassword) { errorElement.classList.remove('hidden'); } else { errorElement.classList.add('hidden'); } } // Add event listeners document.addEventListener('DOMContentLoaded', function() { document.getElementById('newPassword').addEventListener('input', function(e) { checkPasswordStrength(e.target.value); }); document.getElementById('confirmPassword').addEventListener('input', function() { checkPasswordsMatch(); }); document.getElementById('newPassword').addEventListener('input', function() { checkPasswordsMatch(); }); }); // Проверка силы пароля function checkPasswordStrength(password) { if (!password) { document.getElementById('passwordStrength').className = 'password-strength'; return; } let strength = 0; const checks = { length: password.length >= 8, lowercase: /[a-z]/.test(password), uppercase: /[A-Z]/.test(password), number: /\d/.test(password), special: /[@$!%*?&]/.test(password) }; strength = Object.values(checks).filter(Boolean).length; const strengthBar = document.getElementById('passwordStrength'); strengthBar.className = 'password-strength'; if (strength <= 2) { strengthBar.classList.add('weak'); } else if (strength <= 4) { strengthBar.classList.add('medium'); } else { strengthBar.classList.add('strong'); } } // Обработка изменения пароля document.getElementById('newPassword').addEventListener('input', function(e) { checkPasswordStrength(e.target.value); }); // Обработка отправки формы document.getElementById('profileForm').addEventListener('submit', async function(e) { e.preventDefault(); const formData = new FormData(); let hasChanges = false; // Получаем значения полей const fields = { first_name: document.getElementById('firstName').value, last_name: document.getElementById('lastName').value, middle_name: document.getElementById('middleName').value, username: document.getElementById('nickname').value, email: document.getElementById('email').value }; // Проверяем, какие поля изменились for (const [key, value] of Object.entries(fields)) { if (value !== originalData[key]) { formData.append(key, value); hasChanges = true; } } // Проверяем пароль const currentPassword = document.getElementById('currentPassword').value; const newPassword = document.getElementById('newPassword').value; const confirmPassword = document.getElementById('confirmPassword').value; if (newPassword) { if (!currentPassword) { alert('Please enter your current password to change password'); return; } if (newPassword !== confirmPassword) { alert('New passwords do not match'); return; } formData.append('current_password', currentPassword); formData.append('new_password', newPassword); hasChanges = true; } // Проверяем аватар const avatarInput = document.getElementById('avatar-upload'); if (avatarInput.files.length > 0) { formData.append('avatar', avatarInput.files[0]); hasChanges = true; } // Если нет изменений, не отправляем запрос if (!hasChanges) { window.location.href = 'index.html'; return; } try { const response = await fetchWithAuth('http://127.0.0.1:8000/api/users/update_user_info/', { method: 'POST', body: formData }); if (response.ok) { window.location.href = 'index.html'; } else { const errorData = await response.json(); alert(errorData.message || 'Error updating profile'); } } catch (error) { console.error('Error:', error); alert('Error updating profile'); } }); // Предпросмотр аватара document.getElementById('avatar-upload').addEventListener('change', function(e) { const file = e.target.files[0]; if (file) { const reader = new FileReader(); reader.onload = function(e) { document.getElementById('avatar').src = e.target.result; }; reader.readAsDataURL(file); } }); // Функция переключения видимости пароля function togglePassword(inputId) { const input = document.getElementById(inputId); const button = input.nextElementSibling; const svg = button.querySelector('svg'); if (input.type === 'password') { input.type = 'text'; svg.innerHTML = ` <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21" /> `; } else { input.type = 'password'; svg.innerHTML = ` <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" /> `; } } function setTheme(dark) { if (dark) { document.documentElement.classList.add('dark'); localStorage.theme = 'dark'; } else { document.documentElement.classList.remove('dark'); localStorage.theme = 'light'; } } </script> </body> </html>