238 lines
No EOL
9.1 KiB
HTML
238 lines
No EOL
9.1 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Profile - Flask Files{% endblock %}
|
|
|
|
{% block extra_css %}
|
|
<style>
|
|
.profile-container {
|
|
max-width: 800px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
.profile-tabs {
|
|
display: flex;
|
|
border-bottom: 1px solid var(--border-color);
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
|
|
.profile-tab {
|
|
padding: 0.75rem 1.5rem;
|
|
cursor: pointer;
|
|
background: none;
|
|
border: none;
|
|
font-weight: 500;
|
|
border-bottom: 2px solid transparent;
|
|
}
|
|
|
|
.profile-tab.active {
|
|
color: var(--primary-color);
|
|
border-bottom-color: var(--primary-color);
|
|
}
|
|
|
|
.tab-content {
|
|
display: none;
|
|
}
|
|
|
|
.tab-content.active {
|
|
display: block;
|
|
}
|
|
|
|
.setting-group {
|
|
margin-bottom: 2rem;
|
|
}
|
|
|
|
.setting-group h3 {
|
|
margin-bottom: 1rem;
|
|
padding-bottom: 0.5rem;
|
|
border-bottom: 1px solid var(--border-color);
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<section class="profile-container">
|
|
<h2>User Profile</h2>
|
|
|
|
<div class="profile-tabs">
|
|
<button class="profile-tab active" data-tab="profile">Profile</button>
|
|
<button class="profile-tab" data-tab="appearance">Appearance</button>
|
|
<button class="profile-tab" data-tab="security">Security</button>
|
|
</div>
|
|
|
|
<!-- Profile Tab -->
|
|
<div class="tab-content active" id="profile-tab">
|
|
<div class="profile-card">
|
|
<div class="profile-header">
|
|
<div class="avatar">
|
|
{{ current_user.username[0].upper() }}
|
|
</div>
|
|
<div class="user-info">
|
|
<h3>{{ current_user.username }}</h3>
|
|
<p>Member since {{ current_user.created_at.strftime('%B %Y') if current_user.created_at else
|
|
'Unknown' }}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="profile-stats">
|
|
<div class="stat-item">
|
|
<span class="stat-value">{{ current_user.files.filter_by(is_folder=False).count() }}</span>
|
|
<span class="stat-label">Files</span>
|
|
</div>
|
|
<div class="stat-item">
|
|
<span class="stat-value">{{ current_user.files.filter_by(is_folder=True).count() }}</span>
|
|
<span class="stat-label">Folders</span>
|
|
</div>
|
|
<div class="stat-item">
|
|
<span class="stat-value">{{ current_user.shares.count() }}</span>
|
|
<span class="stat-label">Shares</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="setting-group">
|
|
<h3>Edit Profile</h3>
|
|
<form id="username-form" method="POST" action="{{ url_for('auth.update_profile') }}">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
|
<div class="form-group">
|
|
<label for="username">Username</label>
|
|
<input type="text" id="username" name="username" class="form-control"
|
|
value="{{ current_user.username }}">
|
|
</div>
|
|
<div class="form-actions">
|
|
<button type="submit" class="btn primary">Save Changes</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Appearance Tab -->
|
|
<div class="tab-content" id="appearance-tab">
|
|
<div class="setting-group">
|
|
<h3>Theme Settings</h3>
|
|
<form id="theme-form" method="POST" action="{{ url_for('auth.update_preferences') }}">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
|
<div class="form-group">
|
|
<label>Default Theme</label>
|
|
<div class="theme-options">
|
|
<label class="radio-container">
|
|
<input type="radio" name="theme_preference" value="light" {% if theme_preference=='light'
|
|
%}checked{% endif %}>
|
|
<span class="radio-label">Light</span>
|
|
</label>
|
|
<label class="radio-container">
|
|
<input type="radio" name="theme_preference" value="dark" {% if theme_preference=='dark'
|
|
%}checked{% endif %}>
|
|
<span class="radio-label">Dark</span>
|
|
</label>
|
|
<label class="radio-container">
|
|
<input type="radio" name="theme_preference" value="system" {% if theme_preference=='system'
|
|
or not theme_preference %}checked{% endif %}>
|
|
<span class="radio-label">Use System Preference</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div class="form-actions">
|
|
<button type="submit" class="btn primary">Save Preferences</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Security Tab -->
|
|
<div class="tab-content" id="security-tab">
|
|
<div class="setting-group">
|
|
<h3>Change Password</h3>
|
|
<form id="password-form" method="POST" action="{{ url_for('auth.change_password') }}">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
|
<div class="form-group">
|
|
<label for="current_password">Current Password</label>
|
|
<input type="password" id="current_password" name="current_password" class="form-control" required>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="new_password">New Password</label>
|
|
<input type="password" id="new_password" name="new_password" class="form-control" required>
|
|
<div class="password-strength" id="password-strength"></div>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="confirm_password">Confirm New Password</label>
|
|
<input type="password" id="confirm_password" name="confirm_password" class="form-control" required>
|
|
</div>
|
|
<div class="form-actions">
|
|
<button type="submit" class="btn primary">Change Password</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
{% endblock %}
|
|
|
|
{% block extra_js %}
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
// Tab switching
|
|
const tabs = document.querySelectorAll('.profile-tab');
|
|
const tabContents = document.querySelectorAll('.tab-content');
|
|
|
|
tabs.forEach(tab => {
|
|
tab.addEventListener('click', function () {
|
|
const tabId = this.getAttribute('data-tab');
|
|
|
|
// Update active tab
|
|
tabs.forEach(t => t.classList.remove('active'));
|
|
this.classList.add('active');
|
|
|
|
// Update active content
|
|
tabContents.forEach(content => {
|
|
content.classList.remove('active');
|
|
if (content.id === `${tabId}-tab`) {
|
|
content.classList.add('active');
|
|
}
|
|
});
|
|
});
|
|
});
|
|
|
|
// Password strength meter
|
|
const passwordInput = document.getElementById('new_password');
|
|
const strengthIndicator = document.getElementById('password-strength');
|
|
|
|
if (passwordInput && strengthIndicator) {
|
|
passwordInput.addEventListener('input', function () {
|
|
const password = this.value;
|
|
let strength = 0;
|
|
|
|
if (password.length >= 8) strength += 1;
|
|
if (password.match(/[a-z]/) && password.match(/[A-Z]/)) strength += 1;
|
|
if (password.match(/\d/)) strength += 1;
|
|
if (password.match(/[^a-zA-Z\d]/)) strength += 1;
|
|
|
|
// Update the strength indicator
|
|
strengthIndicator.className = 'password-strength';
|
|
if (password.length === 0) {
|
|
strengthIndicator.textContent = '';
|
|
} else if (strength < 2) {
|
|
strengthIndicator.textContent = 'Weak';
|
|
strengthIndicator.classList.add('weak');
|
|
} else if (strength < 4) {
|
|
strengthIndicator.textContent = 'Moderate';
|
|
strengthIndicator.classList.add('moderate');
|
|
} else {
|
|
strengthIndicator.textContent = 'Strong';
|
|
strengthIndicator.classList.add('strong');
|
|
}
|
|
});
|
|
}
|
|
|
|
// Password confirmation matching
|
|
const confirmInput = document.getElementById('confirm_password');
|
|
if (passwordInput && confirmInput) {
|
|
confirmInput.addEventListener('input', function () {
|
|
if (passwordInput.value !== this.value) {
|
|
this.setCustomValidity('Passwords must match');
|
|
} else {
|
|
this.setCustomValidity('');
|
|
}
|
|
});
|
|
}
|
|
});
|
|
</script>
|
|
{% endblock %} |