1183 lines
33 KiB
HTML
1183 lines
33 KiB
HTML
{% extends "base_authenticated.html" %} {% block title %}My Profile - QR Code Management{%
|
|
endblock %} {% block content %}
|
|
<div class="profile-page">
|
|
<div class="profile-header">
|
|
<div class="profile-avatar">
|
|
<div class="avatar-circle">
|
|
<i class="fas fa-user"></i>
|
|
</div>
|
|
<div class="avatar-status {{ user.role }}">
|
|
<i
|
|
class="fas {{ 'fa-crown' if user.role == 'admin' else 'fa-user-tie' }}"
|
|
></i>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="profile-info">
|
|
<h1>{{ user.full_name }}</h1>
|
|
<div class="profile-meta">
|
|
<div class="meta-item">
|
|
<i class="fas fa-envelope"></i>
|
|
<span>{{ user.email }}</span>
|
|
</div>
|
|
<div class="meta-item">
|
|
<i class="fas fa-user"></i>
|
|
<span>@{{ user.username }}</span>
|
|
</div>
|
|
<div class="meta-item">
|
|
<i class="fas fa-shield-alt"></i>
|
|
<span class="role-badge {{ user.role }}">
|
|
{{ user.role.title() }}
|
|
</span>
|
|
</div>
|
|
<div class="meta-item">
|
|
<i class="fas fa-calendar-plus"></i>
|
|
<span>Member since {{ user.created_date.strftime('%B %Y') }}</span>
|
|
</div>
|
|
{% if user.last_login_date %}
|
|
<div class="meta-item">
|
|
<i class="fas fa-clock"></i>
|
|
<span
|
|
>Last login {{ user.last_login_date.strftime('%Y-%m-%d %H:%M')
|
|
}}</span
|
|
>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="profile-content">
|
|
<!-- Profile Statistics -->
|
|
<div class="profile-stats">
|
|
<div class="stat-card">
|
|
<div class="stat-icon">
|
|
<i class="fas fa-qrcode"></i>
|
|
</div>
|
|
<div class="stat-info">
|
|
<h3>{{ user.created_qr_codes.count() }}</h3>
|
|
<p>QR Codes Created</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="stat-card">
|
|
<div class="stat-icon">
|
|
<i class="fas fa-check-circle"></i>
|
|
</div>
|
|
<div class="stat-info">
|
|
<h3>
|
|
{{ user.created_qr_codes.filter_by(active_status=True).count() }}
|
|
</h3>
|
|
<p>Active QR Codes</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="stat-card">
|
|
<div class="stat-icon">
|
|
<i class="fas fa-calendar-alt"></i>
|
|
</div>
|
|
<div class="stat-info">
|
|
<h3>{{ user.created_date|days_since }}</h3>
|
|
<p>Days as Member</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Profile Management Tabs -->
|
|
<div class="profile-tabs">
|
|
<div class="tab-headers">
|
|
<button class="tab-header active" data-tab="edit-profile">
|
|
<i class="fas fa-edit"></i>
|
|
Edit Profile
|
|
</button>
|
|
<button class="tab-header" data-tab="change-password">
|
|
<i class="fas fa-key"></i>
|
|
Change Password
|
|
</button>
|
|
<button class="tab-header" data-tab="account-settings">
|
|
<i class="fas fa-cog"></i>
|
|
Account Settings
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Edit Profile Tab -->
|
|
<div class="tab-content active" id="edit-profile">
|
|
<div class="tab-card">
|
|
<div class="tab-header-section">
|
|
<h3>
|
|
<i class="fas fa-user-edit"></i>
|
|
Update Profile Information
|
|
</h3>
|
|
<p>
|
|
Keep your profile information up to date for better collaboration
|
|
</p>
|
|
</div>
|
|
|
|
<form method="POST" class="profile-form" id="profileForm">
|
|
<input type="hidden" name="form_type" value="profile" />
|
|
|
|
<div class="form-row">
|
|
<div class="form-group">
|
|
<label for="full_name">
|
|
<i class="fas fa-id-card"></i>
|
|
Full Name
|
|
</label>
|
|
<input
|
|
type="text"
|
|
id="full_name"
|
|
name="full_name"
|
|
value="{{ user.full_name }}"
|
|
required
|
|
/>
|
|
<small class="form-help">Your display name in the system</small>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="email">
|
|
<i class="fas fa-envelope"></i>
|
|
Email Address
|
|
</label>
|
|
<input
|
|
type="email"
|
|
id="email"
|
|
name="email"
|
|
value="{{ user.email }}"
|
|
required
|
|
/>
|
|
<small class="form-help"
|
|
>Used for notifications and account recovery</small
|
|
>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="username_display">
|
|
<i class="fas fa-user"></i>
|
|
Username
|
|
</label>
|
|
<input
|
|
type="text"
|
|
id="username_display"
|
|
value="{{ user.username }}"
|
|
disabled
|
|
/>
|
|
<small class="form-help"
|
|
>Username cannot be changed. Contact admin if needed.</small
|
|
>
|
|
</div>
|
|
|
|
<div class="form-actions">
|
|
<button type="submit" class="btn btn-primary">
|
|
<i class="fas fa-save"></i>
|
|
Save Changes
|
|
</button>
|
|
<button type="reset" class="btn btn-secondary">
|
|
<i class="fas fa-undo"></i>
|
|
Reset
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Change Password Tab -->
|
|
<div class="tab-content" id="change-password">
|
|
<div class="tab-card">
|
|
<div class="tab-header-section">
|
|
<h3>
|
|
<i class="fas fa-shield-alt"></i>
|
|
Change Password
|
|
</h3>
|
|
<p>Ensure your account security with a strong password</p>
|
|
</div>
|
|
|
|
<form method="POST" class="profile-form" id="passwordForm">
|
|
<input type="hidden" name="form_type" value="password" />
|
|
|
|
<div class="form-group">
|
|
<label for="current_password">
|
|
<i class="fas fa-lock"></i>
|
|
Current Password
|
|
</label>
|
|
<input
|
|
type="password"
|
|
id="current_password"
|
|
name="current_password"
|
|
required
|
|
/>
|
|
<small class="form-help"
|
|
>Enter your current password for verification</small
|
|
>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="new_password">
|
|
<i class="fas fa-key"></i>
|
|
New Password
|
|
</label>
|
|
<input
|
|
type="password"
|
|
id="new_password"
|
|
name="new_password"
|
|
required
|
|
minlength="6"
|
|
/>
|
|
<div class="password-strength" id="passwordStrength"></div>
|
|
<small class="form-help"
|
|
>Minimum 6 characters with mixed case, numbers, and
|
|
symbols</small
|
|
>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="confirm_password">
|
|
<i class="fas fa-check-circle"></i>
|
|
Confirm New Password
|
|
</label>
|
|
<input
|
|
type="password"
|
|
id="confirm_password"
|
|
name="confirm_password"
|
|
required
|
|
/>
|
|
<div class="password-match" id="passwordMatch"></div>
|
|
</div>
|
|
|
|
<div class="security-tips">
|
|
<h4>
|
|
<i class="fas fa-lightbulb"></i>
|
|
Password Security Tips
|
|
</h4>
|
|
<ul>
|
|
<li>Use at least 8 characters</li>
|
|
<li>Include uppercase and lowercase letters</li>
|
|
<li>Add numbers and special characters</li>
|
|
<li>Avoid common words or personal information</li>
|
|
<li>Don't reuse passwords from other accounts</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div class="form-actions">
|
|
<button type="submit" class="btn btn-primary">
|
|
<i class="fas fa-shield-alt"></i>
|
|
Update Password
|
|
</button>
|
|
<button type="reset" class="btn btn-secondary">
|
|
<i class="fas fa-times"></i>
|
|
Cancel
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Account Settings Tab -->
|
|
<div class="tab-content" id="account-settings">
|
|
<div class="tab-card">
|
|
<div class="tab-header-section">
|
|
<h3>
|
|
<i class="fas fa-cog"></i>
|
|
Account Information
|
|
</h3>
|
|
<p>View your account details and system information</p>
|
|
</div>
|
|
|
|
<div class="settings-grid">
|
|
<div class="setting-item">
|
|
<div class="setting-label">
|
|
<i class="fas fa-id-badge"></i>
|
|
Account ID
|
|
</div>
|
|
<div class="setting-value">{{ user.id }}</div>
|
|
</div>
|
|
|
|
<div class="setting-item">
|
|
<div class="setting-label">
|
|
<i class="fas fa-user-shield"></i>
|
|
Role
|
|
</div>
|
|
<div class="setting-value">
|
|
<span class="role-badge {{ user.role }}">
|
|
{{ user.role.title() }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="setting-item">
|
|
<div class="setting-label">
|
|
<i class="fas fa-calendar-plus"></i>
|
|
Account Created
|
|
</div>
|
|
<div class="setting-value">
|
|
{{ user.created_date.strftime('%B %d, %Y at %H:%M') }}
|
|
</div>
|
|
</div>
|
|
|
|
{% if user.creator %}
|
|
<div class="setting-item">
|
|
<div class="setting-label">
|
|
<i class="fas fa-user-plus"></i>
|
|
Created By
|
|
</div>
|
|
<div class="setting-value">{{ user.creator.full_name }}</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<div class="setting-item">
|
|
<div class="setting-label">
|
|
<i class="fas fa-toggle-on"></i>
|
|
Account Status
|
|
</div>
|
|
<div class="setting-value">
|
|
<span
|
|
class="status-badge {{ 'active' if user.active_status else 'inactive' }}"
|
|
>
|
|
<i
|
|
class="fas {{ 'fa-check-circle' if user.active_status else 'fa-times-circle' }}"
|
|
></i>
|
|
{{ 'Active' if user.active_status else 'Inactive' }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
{% if user.last_login_date %}
|
|
<div class="setting-item">
|
|
<div class="setting-label">
|
|
<i class="fas fa-clock"></i>
|
|
Last Login
|
|
</div>
|
|
<div class="setting-value">
|
|
{{ user.last_login_date.strftime('%B %d, %Y at %H:%M') }} ({{
|
|
user.last_login_date|time_ago }})
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<!-- Account Actions -->
|
|
<div class="account-actions">
|
|
<h4>
|
|
<i class="fas fa-tools"></i>
|
|
Account Actions
|
|
</h4>
|
|
|
|
<div class="action-buttons">
|
|
<button type="button" class="btn btn-info" onclick="exportData()">
|
|
<i class="fas fa-download"></i>
|
|
Export My Data
|
|
</button>
|
|
|
|
{% if user.role != 'admin' %}
|
|
<button
|
|
type="button"
|
|
class="btn btn-warning"
|
|
onclick="requestAccountDeletion()"
|
|
>
|
|
<i class="fas fa-exclamation-triangle"></i>
|
|
Request Account Deletion
|
|
</button>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %} {% block extra_scripts %}
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Tab functionality
|
|
const tabHeaders = document.querySelectorAll('.tab-header');
|
|
const tabContents = document.querySelectorAll('.tab-content');
|
|
|
|
tabHeaders.forEach(header => {
|
|
header.addEventListener('click', function() {
|
|
const tabName = this.dataset.tab;
|
|
|
|
// Remove active class from all tabs
|
|
tabHeaders.forEach(h => h.classList.remove('active'));
|
|
tabContents.forEach(c => c.classList.remove('active'));
|
|
|
|
// Add active class to clicked tab
|
|
this.classList.add('active');
|
|
document.getElementById(tabName).classList.add('active');
|
|
});
|
|
});
|
|
|
|
// Password form handling
|
|
const passwordForm = document.getElementById('passwordForm');
|
|
const newPasswordInput = document.getElementById('new_password');
|
|
const confirmPasswordInput = document.getElementById('confirm_password');
|
|
const passwordStrength = document.getElementById('passwordStrength');
|
|
const passwordMatch = document.getElementById('passwordMatch');
|
|
|
|
// Password strength checker
|
|
if (newPasswordInput) {
|
|
newPasswordInput.addEventListener('input', function() {
|
|
const password = this.value;
|
|
let strength = 0;
|
|
let feedback = [];
|
|
|
|
// Check password criteria
|
|
if (password.length >= 6) strength++;
|
|
else feedback.push('At least 6 characters');
|
|
|
|
if (password.length >= 8) strength++;
|
|
else if (password.length >= 6) feedback.push('8+ characters recommended');
|
|
|
|
if (/[A-Z]/.test(password)) strength++;
|
|
else feedback.push('One uppercase letter');
|
|
|
|
if (/[a-z]/.test(password)) strength++;
|
|
else feedback.push('One lowercase letter');
|
|
|
|
if (/[0-9]/.test(password)) strength++;
|
|
else feedback.push('One number');
|
|
|
|
if (/[^A-Za-z0-9]/.test(password)) strength++;
|
|
else feedback.push('One special character');
|
|
|
|
// Update strength display
|
|
let strengthText = '';
|
|
let strengthClass = '';
|
|
|
|
if (strength < 3) {
|
|
strengthText = 'Weak';
|
|
strengthClass = 'weak';
|
|
} else if (strength < 5) {
|
|
strengthText = 'Medium';
|
|
strengthClass = 'medium';
|
|
} else {
|
|
strengthText = 'Strong';
|
|
strengthClass = 'strong';
|
|
}
|
|
|
|
passwordStrength.className = `password-strength ${strengthClass}`;
|
|
passwordStrength.textContent = `Strength: ${strengthText}`;
|
|
|
|
if (feedback.length > 0 && password.length > 0) {
|
|
passwordStrength.textContent += ` (Missing: ${feedback.join(', ')})`;
|
|
}
|
|
|
|
// Check password match
|
|
checkPasswordMatch();
|
|
});
|
|
}
|
|
|
|
// Password match checker
|
|
function checkPasswordMatch() {
|
|
if (confirmPasswordInput && confirmPasswordInput.value) {
|
|
if (newPasswordInput.value === confirmPasswordInput.value) {
|
|
passwordMatch.textContent = '✓ Passwords match';
|
|
passwordMatch.className = 'password-match match';
|
|
} else {
|
|
passwordMatch.textContent = '✗ Passwords do not match';
|
|
passwordMatch.className = 'password-match no-match';
|
|
}
|
|
} else {
|
|
passwordMatch.textContent = '';
|
|
passwordMatch.className = 'password-match';
|
|
}
|
|
}
|
|
|
|
if (confirmPasswordInput) {
|
|
confirmPasswordInput.addEventListener('input', checkPasswordMatch);
|
|
}
|
|
|
|
// Form validation
|
|
if (passwordForm) {
|
|
passwordForm.addEventListener('submit', function(e) {
|
|
const currentPassword = document.getElementById('current_password').value;
|
|
const newPassword = newPasswordInput.value;
|
|
const confirmPassword = confirmPasswordInput.value;
|
|
|
|
if (!currentPassword) {
|
|
e.preventDefault();
|
|
alert('Please enter your current password.');
|
|
return;
|
|
}
|
|
|
|
if (newPassword.length < 6) {
|
|
e.preventDefault();
|
|
alert('New password must be at least 6 characters long.');
|
|
return;
|
|
}
|
|
|
|
if (newPassword !== confirmPassword) {
|
|
e.preventDefault();
|
|
alert('New passwords do not match.');
|
|
return;
|
|
}
|
|
|
|
if (currentPassword === newPassword) {
|
|
e.preventDefault();
|
|
alert('New password must be different from current password.');
|
|
return;
|
|
}
|
|
|
|
// Show loading state
|
|
const submitBtn = passwordForm.querySelector('button[type="submit"]');
|
|
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Updating Password...';
|
|
submitBtn.disabled = true;
|
|
});
|
|
}
|
|
|
|
// Profile form handling
|
|
const profileForm = document.getElementById('profileForm');
|
|
if (profileForm) {
|
|
profileForm.addEventListener('submit', function(e) {
|
|
const fullName = document.getElementById('full_name').value.trim();
|
|
const email = document.getElementById('email').value.trim();
|
|
|
|
if (!fullName || fullName.length < 2) {
|
|
e.preventDefault();
|
|
alert('Full name must be at least 2 characters long.');
|
|
return;
|
|
}
|
|
|
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
if (!email || !emailRegex.test(email)) {
|
|
e.preventDefault();
|
|
alert('Please enter a valid email address.');
|
|
return;
|
|
}
|
|
|
|
// Show loading state
|
|
const submitBtn = profileForm.querySelector('button[type="submit"]');
|
|
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Saving Changes...';
|
|
submitBtn.disabled = true;
|
|
});
|
|
}
|
|
|
|
// Auto-capitalize full name
|
|
const fullNameInput = document.getElementById('full_name');
|
|
if (fullNameInput) {
|
|
fullNameInput.addEventListener('input', function() {
|
|
this.value = this.value.replace(/\b\w/g, l => l.toUpperCase());
|
|
});
|
|
}
|
|
|
|
// Form reset handling
|
|
const resetButtons = document.querySelectorAll('button[type="reset"]');
|
|
resetButtons.forEach(button => {
|
|
button.addEventListener('click', function(e) {
|
|
if (!confirm('Are you sure you want to reset all changes?')) {
|
|
e.preventDefault();
|
|
}
|
|
});
|
|
});
|
|
});
|
|
|
|
// Export user data
|
|
function exportData() {
|
|
const userData = {
|
|
id: {{ user.id }},
|
|
full_name: "{{ user.full_name }}",
|
|
email: "{{ user.email }}",
|
|
username: "{{ user.username }}",
|
|
role: "{{ user.role }}",
|
|
created_date: "{{ user.created_date.isoformat() }}",
|
|
last_login_date: "{{ user.last_login_date.isoformat() if user.last_login_date else 'Never' }}",
|
|
qr_codes_created: {{ user.created_qr_codes.count() }},
|
|
active_qr_codes: {{ user.created_qr_codes.filter_by(active_status=True).count() }}
|
|
};
|
|
|
|
const dataStr = JSON.stringify(userData, null, 2);
|
|
const dataBlob = new Blob([dataStr], { type: 'application/json' });
|
|
|
|
const link = document.createElement('a');
|
|
link.href = URL.createObjectURL(dataBlob);
|
|
link.download = 'my_account_data.json';
|
|
document.body.appendChild(link);
|
|
link.click();
|
|
document.body.removeChild(link);
|
|
|
|
// Show success message
|
|
showToast('Account data exported successfully!', 'success');
|
|
}
|
|
|
|
// Request account deletion
|
|
function requestAccountDeletion() {
|
|
const confirmed = confirm(
|
|
'Are you sure you want to request account deletion?\n\n' +
|
|
'This action will:\n' +
|
|
'• Deactivate your account\n' +
|
|
'• Remove access to QR codes you created\n' +
|
|
'• Require admin approval for deletion\n\n' +
|
|
'This action cannot be undone easily.'
|
|
);
|
|
|
|
if (confirmed) {
|
|
const reason = prompt('Please provide a reason for account deletion (optional):');
|
|
|
|
// In a real application, this would send a request to the server
|
|
alert('Account deletion request submitted. An administrator will review your request.');
|
|
showToast('Account deletion request submitted', 'info');
|
|
}
|
|
}
|
|
|
|
// Toast notification function
|
|
function showToast(message, type = 'info') {
|
|
const toast = document.createElement('div');
|
|
toast.className = `toast toast-${type}`;
|
|
toast.innerHTML = `
|
|
<div class="toast-content">
|
|
<i class="fas ${getToastIcon(type)}"></i>
|
|
<span>${message}</span>
|
|
</div>
|
|
`;
|
|
|
|
document.body.appendChild(toast);
|
|
|
|
// Animate in
|
|
setTimeout(() => toast.classList.add('show'), 100);
|
|
|
|
// Auto remove
|
|
setTimeout(() => {
|
|
toast.classList.remove('show');
|
|
setTimeout(() => {
|
|
if (document.body.contains(toast)) {
|
|
document.body.removeChild(toast);
|
|
}
|
|
}, 300);
|
|
}, 3000);
|
|
}
|
|
|
|
function getToastIcon(type) {
|
|
const icons = {
|
|
success: 'fa-check-circle',
|
|
error: 'fa-exclamation-circle',
|
|
warning: 'fa-exclamation-triangle',
|
|
info: 'fa-info-circle'
|
|
};
|
|
return icons[type] || icons.info;
|
|
}
|
|
</script>
|
|
|
|
<style>
|
|
/* Profile page styles */
|
|
.profile-page {
|
|
max-width: 1000px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
.profile-header {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 2rem;
|
|
padding: 2rem;
|
|
background: linear-gradient(
|
|
135deg,
|
|
var(--primary-color),
|
|
var(--primary-hover)
|
|
);
|
|
color: var(--white);
|
|
border-radius: var(--radius-xl);
|
|
margin-bottom: 2rem;
|
|
box-shadow: var(--shadow-lg);
|
|
}
|
|
|
|
.profile-avatar {
|
|
position: relative;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.avatar-circle {
|
|
width: 100px;
|
|
height: 100px;
|
|
background: rgba(255, 255, 255, 0.2);
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 2.5rem;
|
|
border: 3px solid rgba(255, 255, 255, 0.3);
|
|
}
|
|
|
|
.avatar-status {
|
|
position: absolute;
|
|
bottom: 0;
|
|
right: 0;
|
|
width: 30px;
|
|
height: 30px;
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 0.875rem;
|
|
border: 2px solid var(--white);
|
|
}
|
|
|
|
.avatar-status.admin {
|
|
background: #fbbf24;
|
|
color: #92400e;
|
|
}
|
|
|
|
.avatar-status.staff {
|
|
background: var(--gray-400);
|
|
color: var(--white);
|
|
}
|
|
|
|
.profile-info h1 {
|
|
font-size: 2rem;
|
|
font-weight: 700;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.profile-meta {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
gap: 0.75rem;
|
|
}
|
|
|
|
.meta-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
font-size: 0.875rem;
|
|
opacity: 0.9;
|
|
}
|
|
|
|
.role-badge {
|
|
padding: 0.25rem 0.75rem;
|
|
border-radius: var(--radius);
|
|
font-size: 0.75rem;
|
|
font-weight: 600;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.role-badge.admin {
|
|
background: #fbbf24;
|
|
color: #92400e;
|
|
}
|
|
|
|
.role-badge.staff {
|
|
background: rgba(255, 255, 255, 0.2);
|
|
color: var(--white);
|
|
}
|
|
|
|
.profile-stats {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
gap: 1.5rem;
|
|
margin-bottom: 2rem;
|
|
}
|
|
|
|
.stat-card {
|
|
background: var(--white);
|
|
padding: 1.5rem;
|
|
border-radius: var(--radius-xl);
|
|
box-shadow: var(--shadow);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 1rem;
|
|
transition: var(--transition);
|
|
}
|
|
|
|
.stat-card:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: var(--shadow-lg);
|
|
}
|
|
|
|
.stat-icon {
|
|
width: 50px;
|
|
height: 50px;
|
|
background: linear-gradient(
|
|
135deg,
|
|
var(--primary-color),
|
|
var(--primary-hover)
|
|
);
|
|
color: var(--white);
|
|
border-radius: var(--radius-lg);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 1.25rem;
|
|
}
|
|
|
|
.stat-info h3 {
|
|
font-size: 1.75rem;
|
|
font-weight: 700;
|
|
color: var(--gray-900);
|
|
margin-bottom: 0.25rem;
|
|
}
|
|
|
|
.stat-info p {
|
|
color: var(--gray-500);
|
|
font-size: 0.875rem;
|
|
}
|
|
|
|
.profile-tabs {
|
|
background: var(--white);
|
|
border-radius: var(--radius-xl);
|
|
box-shadow: var(--shadow);
|
|
overflow: hidden;
|
|
}
|
|
|
|
.tab-headers {
|
|
display: flex;
|
|
border-bottom: 1px solid var(--gray-200);
|
|
}
|
|
|
|
.tab-header {
|
|
flex: 1;
|
|
padding: 1rem 1.5rem;
|
|
background: none;
|
|
border: none;
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 0.5rem;
|
|
font-weight: 500;
|
|
color: var(--gray-600);
|
|
transition: var(--transition);
|
|
border-bottom: 3px solid transparent;
|
|
}
|
|
|
|
.tab-header:hover {
|
|
background: var(--gray-50);
|
|
color: var(--gray-900);
|
|
}
|
|
|
|
.tab-header.active {
|
|
color: var(--primary-color);
|
|
border-bottom-color: var(--primary-color);
|
|
background: var(--gray-50);
|
|
}
|
|
|
|
.tab-content {
|
|
display: none;
|
|
padding: 2rem;
|
|
}
|
|
|
|
.tab-content.active {
|
|
display: block;
|
|
}
|
|
|
|
.tab-card {
|
|
max-width: 600px;
|
|
}
|
|
|
|
.tab-header-section {
|
|
margin-bottom: 2rem;
|
|
padding-bottom: 1rem;
|
|
border-bottom: 1px solid var(--gray-200);
|
|
}
|
|
|
|
.tab-header-section h3 {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
font-size: 1.25rem;
|
|
font-weight: 600;
|
|
color: var(--gray-900);
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.tab-header-section p {
|
|
color: var(--gray-500);
|
|
font-size: 0.875rem;
|
|
}
|
|
|
|
.form-row {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 1.5rem;
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
|
|
.security-tips {
|
|
margin: 1.5rem 0;
|
|
padding: 1rem;
|
|
background: var(--gray-50);
|
|
border-radius: var(--radius-lg);
|
|
border-left: 4px solid var(--primary-color);
|
|
}
|
|
|
|
.security-tips h4 {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
font-size: 0.875rem;
|
|
font-weight: 600;
|
|
color: var(--gray-700);
|
|
margin-bottom: 0.75rem;
|
|
}
|
|
|
|
.security-tips ul {
|
|
list-style: none;
|
|
padding: 0;
|
|
margin: 0;
|
|
}
|
|
|
|
.security-tips li {
|
|
padding: 0.25rem 0;
|
|
font-size: 0.875rem;
|
|
color: var(--gray-600);
|
|
position: relative;
|
|
padding-left: 1rem;
|
|
}
|
|
|
|
.security-tips li::before {
|
|
content: "•";
|
|
position: absolute;
|
|
left: 0;
|
|
color: var(--primary-color);
|
|
font-weight: bold;
|
|
}
|
|
|
|
.settings-grid {
|
|
display: grid;
|
|
gap: 1rem;
|
|
margin-bottom: 2rem;
|
|
}
|
|
|
|
.setting-item {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 1rem;
|
|
background: var(--gray-50);
|
|
border-radius: var(--radius);
|
|
border: 1px solid var(--gray-200);
|
|
}
|
|
|
|
.setting-label {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
font-weight: 500;
|
|
color: var(--gray-700);
|
|
}
|
|
|
|
.setting-value {
|
|
font-weight: 500;
|
|
color: var(--gray-900);
|
|
}
|
|
|
|
.status-badge {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.25rem;
|
|
padding: 0.25rem 0.75rem;
|
|
border-radius: var(--radius);
|
|
font-size: 0.75rem;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.status-badge.active {
|
|
background: rgba(5, 150, 105, 0.1);
|
|
color: var(--success-color);
|
|
}
|
|
|
|
.status-badge.inactive {
|
|
background: rgba(220, 38, 38, 0.1);
|
|
color: var(--danger-color);
|
|
}
|
|
|
|
.account-actions {
|
|
margin-top: 2rem;
|
|
padding-top: 2rem;
|
|
border-top: 1px solid var(--gray-200);
|
|
}
|
|
|
|
.account-actions h4 {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
font-size: 1rem;
|
|
font-weight: 600;
|
|
color: var(--gray-700);
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.action-buttons {
|
|
display: flex;
|
|
gap: 1rem;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
/* Toast styles */
|
|
.toast {
|
|
position: fixed;
|
|
top: 20px;
|
|
right: 20px;
|
|
background: var(--white);
|
|
border-radius: var(--radius-lg);
|
|
box-shadow: var(--shadow-xl);
|
|
padding: 1rem;
|
|
z-index: 1000;
|
|
opacity: 0;
|
|
transform: translateX(100%);
|
|
transition: all 0.3s ease;
|
|
border-left: 4px solid;
|
|
}
|
|
|
|
.toast.show {
|
|
opacity: 1;
|
|
transform: translateX(0);
|
|
}
|
|
|
|
.toast.toast-success {
|
|
border-left-color: var(--success-color);
|
|
}
|
|
|
|
.toast.toast-info {
|
|
border-left-color: var(--info-color);
|
|
}
|
|
|
|
.toast-content {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.toast.toast-success .toast-content {
|
|
color: var(--success-color);
|
|
}
|
|
|
|
.toast.toast-info .toast-content {
|
|
color: var(--info-color);
|
|
}
|
|
|
|
/* Form styles */
|
|
.form-group {
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
|
|
.form-group label {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
font-weight: 600;
|
|
color: var(--gray-700);
|
|
margin-bottom: 0.5rem;
|
|
font-size: 0.875rem;
|
|
}
|
|
|
|
.form-group input,
|
|
.form-group select,
|
|
.form-group textarea {
|
|
width: 100%;
|
|
padding: 0.75rem;
|
|
border: 2px solid var(--gray-200);
|
|
border-radius: var(--radius-lg);
|
|
font-size: var(--font-size-base);
|
|
transition: var(--transition);
|
|
background-color: var(--white);
|
|
}
|
|
|
|
.form-group input:focus,
|
|
.form-group select:focus,
|
|
.form-group textarea:focus {
|
|
outline: none;
|
|
border-color: var(--primary-color);
|
|
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
|
|
}
|
|
|
|
.form-group input:disabled {
|
|
background-color: var(--gray-100);
|
|
color: var(--gray-500);
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.form-help {
|
|
display: block;
|
|
margin-top: 0.5rem;
|
|
font-size: 0.75rem;
|
|
color: var(--gray-500);
|
|
}
|
|
|
|
.form-actions {
|
|
display: flex;
|
|
justify-content: flex-start;
|
|
gap: 1rem;
|
|
margin-top: 2rem;
|
|
padding-top: 1.5rem;
|
|
border-top: 1px solid var(--gray-200);
|
|
}
|
|
|
|
.password-strength {
|
|
margin-top: 0.5rem;
|
|
font-size: 0.75rem;
|
|
padding: 0.5rem;
|
|
border-radius: var(--radius);
|
|
transition: var(--transition);
|
|
}
|
|
|
|
.password-strength.weak {
|
|
background-color: rgba(220, 38, 38, 0.1);
|
|
color: var(--danger-color);
|
|
}
|
|
|
|
.password-strength.medium {
|
|
background-color: rgba(217, 119, 6, 0.1);
|
|
color: var(--warning-color);
|
|
}
|
|
|
|
.password-strength.strong {
|
|
background-color: rgba(5, 150, 105, 0.1);
|
|
color: var(--success-color);
|
|
}
|
|
|
|
.password-match {
|
|
margin-top: 0.5rem;
|
|
font-size: 0.75rem;
|
|
padding: 0.5rem;
|
|
border-radius: var(--radius);
|
|
}
|
|
|
|
.password-match.match {
|
|
background-color: rgba(5, 150, 105, 0.1);
|
|
color: var(--success-color);
|
|
}
|
|
|
|
.password-match.no-match {
|
|
background-color: rgba(220, 38, 38, 0.1);
|
|
color: var(--danger-color);
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.profile-header {
|
|
flex-direction: column;
|
|
text-align: center;
|
|
gap: 1rem;
|
|
}
|
|
|
|
.profile-meta {
|
|
grid-template-columns: 1fr;
|
|
text-align: left;
|
|
}
|
|
|
|
.form-row {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.tab-headers {
|
|
flex-direction: column;
|
|
}
|
|
|
|
.tab-header {
|
|
border-bottom: none;
|
|
border-right: 3px solid transparent;
|
|
}
|
|
|
|
.tab-header.active {
|
|
border-bottom: none;
|
|
border-right-color: var(--primary-color);
|
|
}
|
|
|
|
.action-buttons {
|
|
flex-direction: column;
|
|
}
|
|
|
|
.form-actions {
|
|
flex-direction: column;
|
|
}
|
|
}
|
|
</style>
|
|
{% endblock %}
|