Flask-Files/app/templates/auth/profile.html
2025-03-23 01:31:21 +01:00

666 lines
No EOL
25 KiB
HTML

{% extends "base.html" %}
{% block title %}User 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);
}
/* Modal styles */
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 1000;
overflow: auto;
justify-content: center;
align-items: center;
}
.modal.visible {
display: flex;
}
.modal-content {
background: var(--card-bg);
border-radius: var(--border-radius);
width: 100%;
max-width: 500px;
box-shadow: var(--shadow-lg);
margin: 2rem;
}
/* Danger zone */
.dangerous-zone {
background-color: rgba(var(--danger-color-rgb), 0.1);
border-radius: var(--border-radius);
padding: 1.5rem;
margin-top: 2rem;
}
.danger-actions {
display: flex;
flex-wrap: wrap;
gap: 1rem;
margin-top: 1rem;
}
.btn.dangerous {
background-color: var(--danger-color);
color: #fff;
}
.btn.dangerous:hover {
background-color: var(--danger-hover-color);
}
/* Theme toggle */
.theme-options {
display: flex;
gap: 0.5rem;
}
.theme-btn {
padding: 0.5rem 1rem;
border-radius: var(--border-radius);
background: var(--secondary-bg);
border: 1px solid var(--border-color);
cursor: pointer;
}
.theme-btn.active {
background: var(--primary-color);
color: white;
border-color: var(--primary-color);
}
</style>
{% endblock %}
{% block content %}
<div class="container">
<div class="profile-card">
<div class="profile-header">
<h2>User Profile</h2>
<div class="theme-toggle">
<span>Theme:</span>
<div class="theme-options">
<button class="theme-btn {% if theme_preference == 'light' %}active{% endif %}" data-theme="light">
<i class="fas fa-sun"></i> Light
</button>
<button class="theme-btn {% if theme_preference == 'dark' %}active{% endif %}" data-theme="dark">
<i class="fas fa-moon"></i> Dark
</button>
<button class="theme-btn {% if theme_preference == 'system' %}active{% endif %}"
data-theme="system">
<i class="fas fa-desktop"></i> System
</button>
</div>
</div>
</div>
<!-- Tab navigation -->
<div class="profile-tabs">
<button class="profile-tab active" data-tab="account">Account</button>
<button class="profile-tab" data-tab="settings">Settings</button>
</div>
<!-- Account Tab -->
<div class="tab-content active" id="account-tab">
<div class="profile-content">
<div class="profile-stats">
<div class="stat-card">
<div class="stat-icon">
<i class="fas fa-file"></i>
</div>
<div class="stat-info">
<h3 class="stat-title">Files</h3>
<span class="stat-value">{{ current_user.files.filter_by(is_folder=False).count() }}</span>
</div>
</div>
<div class="stat-card">
<div class="stat-icon">
<i class="fas fa-folder"></i>
</div>
<div class="stat-info">
<h3 class="stat-title">Folders</h3>
<span class="stat-value">{{ current_user.files.filter_by(is_folder=True).count() }}</span>
</div>
</div>
<div class="stat-card">
<div class="stat-icon">
<i class="fas fa-share-alt"></i>
</div>
<div class="stat-info">
<h3 class="stat-title">Shares</h3>
<span class="stat-value">{{ current_user.shares.count() if hasattr(current_user, 'shares')
else 0 }}</span>
</div>
</div>
<div class="stat-card">
<div class="stat-icon">
<i class="fas fa-calendar-alt"></i>
</div>
<div class="stat-info">
<h3 class="stat-title">Member Since</h3>
<span class="stat-value">{{ current_user.created_at.strftime('%b %d, %Y') }}</span>
</div>
</div>
</div>
<div class="profile-form">
<h3>Account Information</h3>
<form action="{{ url_for('auth.update_profile') }}" method="post">
<div class="form-group">
<label for="username">Username</label>
<input type="text" id="username" name="username" value="{{ current_user.username }}"
required>
</div>
<div class="form-group">
<label for="email">Email Address</label>
<input type="email" id="email" name="email" value="{{ current_user.email }}" required>
</div>
<div class="form-actions">
<button type="submit" class="btn primary">Save Changes</button>
</div>
</form>
<h3>Change Password</h3>
<form action="{{ url_for('auth.change_password') }}" method="post">
<div class="form-group">
<label for="current_password">Current Password</label>
<input type="password" id="current_password" name="current_password" required>
</div>
<div class="form-group">
<label for="new_password">New Password</label>
<input type="password" id="new_password" name="new_password" required>
</div>
<div class="form-group">
<label for="confirm_password">Confirm New Password</label>
<input type="password" id="confirm_password" name="confirm_password" required>
</div>
<div class="form-actions">
<button type="submit" class="btn primary">Change Password</button>
</div>
</form>
</div>
</div>
</div>
<!-- Settings Tab -->
<div class="tab-content" id="settings-tab">
<div class="settings-content">
<div class="settings-section">
<h3>Appearance</h3>
<div class="setting-group">
<div class="setting-label">File View</div>
<div class="setting-controls">
<div class="view-options">
<button class="view-btn {% if view_preference == 'grid' %}active{% endif %}"
data-view="grid">
<i class="fas fa-th"></i> Grid
</button>
<button class="view-btn {% if view_preference == 'list' %}active{% endif %}"
data-view="list">
<i class="fas fa-list"></i> List
</button>
</div>
</div>
</div>
</div>
<div class="settings-section">
<h3>Notifications</h3>
<div class="setting-group">
<div class="setting-label">Email Notifications</div>
<div class="setting-controls">
<label class="toggle">
<input type="checkbox" id="email-notifications" {% if notifications and
notifications.email %}checked{% endif %}>
<span class="toggle-slider"></span>
</label>
</div>
<div class="setting-description">
Receive email notifications about file shares and new comments
</div>
</div>
</div>
<div class="settings-section">
<h3>Privacy</h3>
<div class="setting-group">
<div class="setting-label">Public Profile</div>
<div class="setting-controls">
<label class="toggle">
<input type="checkbox" id="public-profile" {% if privacy and privacy.public_profile
%}checked{% endif %}>
<span class="toggle-slider"></span>
</label>
</div>
<div class="setting-description">
Allow others to see your profile and shared files
</div>
</div>
<div class="setting-group">
<div class="setting-label">Share Statistics</div>
<div class="setting-controls">
<label class="toggle">
<input type="checkbox" id="share-statistics" {% if privacy and privacy.share_statistics
%}checked{% endif %}>
<span class="toggle-slider"></span>
</label>
</div>
<div class="setting-description">
Collect anonymous usage statistics to improve the service
</div>
</div>
</div>
<div class="settings-section dangerous-zone">
<h3>Danger Zone</h3>
<p class="warning-text">
These actions are permanent and cannot be undone
</p>
<div class="danger-actions">
<button id="delete-files-btn" class="btn dangerous">
<i class="fas fa-trash-alt"></i> Delete All Files
</button>
<button id="delete-account-btn" class="btn dangerous">
<i class="fas fa-user-times"></i> Delete Account
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Delete Files Confirmation Modal -->
<div id="delete-files-modal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h3>Delete All Files</h3>
<button class="modal-close">&times;</button>
</div>
<div class="modal-body">
<p class="warning-text">
Are you sure you want to delete all your files? This action cannot be undone.
</p>
<div class="form-group">
<label for="delete-files-confirm">Type "DELETE" to confirm</label>
<input type="text" id="delete-files-confirm">
</div>
<div class="form-actions">
<button class="btn secondary modal-cancel">Cancel</button>
<button id="confirm-delete-files" class="btn dangerous" disabled>Delete All Files</button>
</div>
</div>
</div>
</div>
<!-- Delete Account Confirmation Modal -->
<div id="delete-account-modal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h3>Delete Account</h3>
<button class="modal-close">&times;</button>
</div>
<div class="modal-body">
<p class="warning-text">
Are you sure you want to delete your account? All your files will be permanently deleted.
This action cannot be undone.
</p>
<div class="form-group">
<label for="delete-account-confirm">Type your username to confirm</label>
<input type="text" id="delete-account-confirm">
</div>
<div class="form-actions">
<button class="btn secondary modal-cancel">Cancel</button>
<button id="confirm-delete-account" class="btn dangerous" disabled>Delete Account</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
document.addEventListener('DOMContentLoaded', function () {
// Make sure modals are hidden on load
document.querySelectorAll('.modal').forEach(modal => {
modal.style.display = 'none';
});
// Tab switching
const tabs = document.querySelectorAll('.profile-tab');
const tabContents = document.querySelectorAll('.tab-content');
tabs.forEach(tab => {
tab.addEventListener('click', function () {
const tabId = this.dataset.tab;
// Hide all tab contents
tabContents.forEach(content => {
content.classList.remove('active');
});
// Show selected tab content
document.getElementById(`${tabId}-tab`).classList.add('active');
// Update active tab
tabs.forEach(t => t.classList.remove('active'));
this.classList.add('active');
});
});
// Theme switching functionality
const themeButtons = document.querySelectorAll('.theme-btn');
themeButtons.forEach(button => {
button.addEventListener('click', function () {
const theme = this.dataset.theme;
setTheme(theme);
// Update active button
themeButtons.forEach(btn => btn.classList.remove('active'));
this.classList.add('active');
// Save preference
fetch('/auth/set_theme', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
},
body: JSON.stringify({ theme: theme })
});
});
});
function setTheme(theme) {
const html = document.documentElement;
if (theme === 'light') {
html.setAttribute('data-theme', 'light');
} else if (theme === 'dark') {
html.setAttribute('data-theme', 'dark');
} else if (theme === 'system') {
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
html.setAttribute('data-theme', prefersDark ? 'dark' : 'light');
}
}
// View preference
const viewButtons = document.querySelectorAll('.view-btn');
viewButtons.forEach(button => {
button.addEventListener('click', function () {
const view = this.dataset.view;
// Update active button
viewButtons.forEach(btn => btn.classList.remove('active'));
this.classList.add('active');
// Save preference
fetch('/auth/set_view_preference', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
},
body: JSON.stringify({ view: view })
});
});
});
// Toggle settings
const toggles = document.querySelectorAll('.toggle input[type="checkbox"]');
toggles.forEach(toggle => {
toggle.addEventListener('change', function () {
const setting = this.id;
const value = this.checked;
fetch('/auth/update_setting', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
},
body: JSON.stringify({
setting: setting,
value: value
})
});
});
});
// Password validation
const passwordForm = document.querySelector('form[action*="change_password"]');
passwordForm.addEventListener('submit', function (e) {
const newPassword = document.getElementById('new_password').value;
const confirmPassword = document.getElementById('confirm_password').value;
if (newPassword !== confirmPassword) {
e.preventDefault();
showAlert('New password and confirmation do not match', 'error');
}
});
// Delete files functionality
const deleteFilesBtn = document.getElementById('delete-files-btn');
const deleteFilesModal = document.getElementById('delete-files-modal');
const deleteFilesConfirmInput = document.getElementById('delete-files-confirm');
const confirmDeleteFilesBtn = document.getElementById('confirm-delete-files');
deleteFilesBtn.addEventListener('click', function () {
openModal(deleteFilesModal);
deleteFilesConfirmInput.value = '';
confirmDeleteFilesBtn.disabled = true;
deleteFilesConfirmInput.focus();
});
deleteFilesConfirmInput.addEventListener('input', function () {
confirmDeleteFilesBtn.disabled = this.value !== 'DELETE';
});
confirmDeleteFilesBtn.addEventListener('click', function () {
if (deleteFilesConfirmInput.value === 'DELETE') {
fetch('/auth/delete_all_files', {
method: 'POST',
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
})
.then(response => response.json())
.then(data => {
closeModal(deleteFilesModal);
if (data.success) {
showAlert('All files have been deleted', 'success');
// Update file count
document.querySelectorAll('.stat-value')[0].textContent = '0';
document.querySelectorAll('.stat-value')[1].textContent = '0';
} else {
showAlert(data.error || 'Failed to delete files', 'error');
}
})
.catch(error => {
showAlert('An error occurred: ' + error, 'error');
});
}
});
// Delete account functionality
const deleteAccountBtn = document.getElementById('delete-account-btn');
const deleteAccountModal = document.getElementById('delete-account-modal');
const deleteAccountConfirmInput = document.getElementById('delete-account-confirm');
const confirmDeleteAccountBtn = document.getElementById('confirm-delete-account');
const usernameToConfirm = "{{ current_user.username }}";
deleteAccountBtn.addEventListener('click', function () {
openModal(deleteAccountModal);
deleteAccountConfirmInput.value = '';
confirmDeleteAccountBtn.disabled = true;
deleteAccountConfirmInput.focus();
});
deleteAccountConfirmInput.addEventListener('input', function () {
confirmDeleteAccountBtn.disabled = this.value !== usernameToConfirm;
});
confirmDeleteAccountBtn.addEventListener('click', function () {
if (deleteAccountConfirmInput.value === usernameToConfirm) {
fetch('/auth/delete_account', {
method: 'POST',
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
})
.then(response => response.json())
.then(data => {
closeModal(deleteAccountModal);
if (data.success) {
showAlert('Your account has been deleted', 'success');
setTimeout(() => {
window.location.href = '/auth/logout';
}, 1500);
} else {
showAlert(data.error || 'Failed to delete account', 'error');
}
})
.catch(error => {
showAlert('An error occurred: ' + error, 'error');
});
}
});
// Modal utilities
function openModal(modal) {
modal.style.display = 'flex';
document.body.classList.add('modal-open');
}
function closeModal(modal) {
modal.style.display = 'none';
document.body.classList.remove('modal-open');
}
// Close modals when clicking outside or on close button
document.addEventListener('click', function (e) {
if (e.target.classList.contains('modal')) {
closeModal(e.target);
} else if (e.target.classList.contains('modal-close') || e.target.classList.contains('modal-cancel')) {
const modal = e.target.closest('.modal');
closeModal(modal);
}
});
// Escape key to close modals
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape') {
document.querySelectorAll('.modal').forEach(modal => {
closeModal(modal);
});
}
});
// Helper function to show alerts
function showAlert(message, type = 'info') {
// Create alerts container if it doesn't exist
let alertsContainer = document.querySelector('.alerts');
if (!alertsContainer) {
alertsContainer = document.createElement('div');
alertsContainer.className = 'alerts';
document.body.appendChild(alertsContainer);
}
// Create alert
const alert = document.createElement('div');
alert.className = `alert ${type}`;
alert.innerHTML = `
<div class="alert-content">${message}</div>
<button class="close" aria-label="Close">&times;</button>
`;
// Add to container
alertsContainer.appendChild(alert);
// Setup dismiss
const closeBtn = alert.querySelector('.close');
closeBtn.addEventListener('click', function () {
alert.classList.add('fade-out');
setTimeout(() => {
alert.remove();
}, 300);
});
// Auto dismiss
setTimeout(() => {
if (alert.parentNode) {
alert.classList.add('fade-out');
setTimeout(() => {
if (alert.parentNode) {
alert.remove();
}
}, 300);
}
}, 5000);
}
});
</script>
{% endblock %}