working folder creation
This commit is contained in:
parent
ea3e92b8b7
commit
b9a82af12f
11 changed files with 2791 additions and 1552 deletions
|
@ -1,6 +1,6 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Profile - Flask Files{% endblock %}
|
||||
{% block title %}User Profile - Flask Files{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
<style>
|
||||
|
@ -46,192 +46,620 @@
|
|||
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 %}
|
||||
<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 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>
|
||||
|
||||
<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>
|
||||
<!-- 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="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 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>
|
||||
|
||||
<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 }}">
|
||||
<!-- 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 class="form-actions">
|
||||
<button type="submit" class="btn primary">Save Changes</button>
|
||||
</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>
|
||||
</form>
|
||||
</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>
|
||||
|
||||
<!-- 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>
|
||||
<!-- 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">×</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>
|
||||
|
||||
<!-- 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>
|
||||
<!-- 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">×</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>
|
||||
</section>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
{% 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.getAttribute('data-tab');
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
||||
// Update active content
|
||||
tabContents.forEach(content => {
|
||||
content.classList.remove('active');
|
||||
if (content.id === `${tabId}-tab`) {
|
||||
content.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 })
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Password strength meter
|
||||
const passwordInput = document.getElementById('new_password');
|
||||
const strengthIndicator = document.getElementById('password-strength');
|
||||
function setTheme(theme) {
|
||||
const html = document.documentElement;
|
||||
|
||||
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');
|
||||
}
|
||||
});
|
||||
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');
|
||||
}
|
||||
}
|
||||
|
||||
// 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('');
|
||||
}
|
||||
// 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">×</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>
|
||||
|
|
436
app/templates/auth/settings.html
Normal file
436
app/templates/auth/settings.html
Normal file
|
@ -0,0 +1,436 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Account Settings - Flask Files{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<div class="settings-card">
|
||||
<div class="settings-header">
|
||||
<h2>Account Settings</h2>
|
||||
<a href="{{ url_for('auth.profile') }}" class="btn secondary">
|
||||
<i class="fas fa-user"></i> Back to Profile
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="settings-content">
|
||||
<div class="settings-section">
|
||||
<h3>Appearance</h3>
|
||||
<div class="theme-settings">
|
||||
<div class="setting-label">Theme</div>
|
||||
<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 class="display-settings">
|
||||
<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>
|
||||
|
||||
<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.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.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.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>
|
||||
|
||||
<!-- 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">×</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">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p class="warning-text">
|
||||
Are you sure you want to delete your account? This will permanently delete all your files and personal
|
||||
data.
|
||||
</p>
|
||||
<div class="form-group">
|
||||
<label for="delete-account-confirm">Type "{{ current_user.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 () {
|
||||
// Theme settings
|
||||
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('{{ url_for("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 settings
|
||||
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
|
||||
localStorage.setItem('files_view', view);
|
||||
|
||||
// Send to server for persistence
|
||||
fetch('{{ url_for("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;
|
||||
|
||||
// Save preference
|
||||
fetch('{{ url_for("auth.update_setting") }}', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
setting: setting,
|
||||
value: value
|
||||
})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
showAlert('Setting updated successfully', 'success');
|
||||
} else {
|
||||
showAlert(data.error || 'Failed to update setting', 'error');
|
||||
// Revert toggle
|
||||
this.checked = !value;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
showAlert('An error occurred: ' + error, 'error');
|
||||
// Revert toggle
|
||||
this.checked = !value;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// 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('{{ url_for("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');
|
||||
setTimeout(() => {
|
||||
window.location.href = '{{ url_for("files.browser") }}';
|
||||
}, 1500);
|
||||
} 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('{{ url_for("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 = '{{ url_for("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.classList.add('visible');
|
||||
document.body.classList.add('modal-open');
|
||||
}
|
||||
|
||||
function closeModal(modal) {
|
||||
modal.classList.remove('visible');
|
||||
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.visible').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">×</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 %}
|
Loading…
Add table
Add a link
Reference in a new issue