Files
TimeTracker/app/templates/auth/edit_profile.html
Dries Peeters 6559dd948b fix: resolve profile picture upload issues
- Add client_max_body_size 10M to nginx config to fix 413 error
- Add JavaScript preview for profile picture selection
- Include client-side validation for file size and type
2025-10-22 10:10:14 +02:00

95 lines
5.0 KiB
HTML

{% extends "base.html" %}
{% block content %}
<div class="flex flex-col md:flex-row justify-between items-start md:items-center mb-6">
<div>
<h1 class="text-2xl font-bold">Edit Profile</h1>
<p class="text-text-muted-light dark:text-text-muted-dark">Update your personal information.</p>
</div>
</div>
<div class="bg-card-light dark:bg-card-dark p-6 rounded-lg shadow">
<form method="POST" enctype="multipart/form-data">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="space-y-6">
<div class="flex items-center gap-4">
<img id="avatar-preview" src="{{ current_user.get_avatar_url() or url_for('static', filename='images/avatar-default.svg') }}" alt="{{ current_user.display_name }}" class="w-16 h-16 rounded-full object-cover bg-gray-200 dark:bg-gray-700">
<div class="flex-1">
<label for="avatar" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Profile picture</label>
<input type="file" name="avatar" id="avatar" accept="image/png,image/jpeg,image/jpg,image/gif,image/webp" class="mt-1 block w-full text-sm text-gray-900 dark:text-gray-100 file:mr-4 file:py-2 file:px-4 file:rounded file:border-0 file:text-sm file:font-semibold file:bg-primary file:text-white hover:file:bg-primary/90" onchange="previewAvatar(this)">
<p class="mt-1 text-xs text-text-muted-light dark:text-text-muted-dark">PNG, JPG, GIF, or WEBP up to 5MB.</p>
{% if current_user.has_avatar() %}
<form method="POST" action="{{ url_for('auth.remove_avatar') }}" class="mt-2">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button type="submit" class="text-red-600 hover:text-red-700 text-sm">Remove current picture</button>
</form>
{% endif %}
</div>
</div>
<div>
<label for="username" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Username</label>
<input type="text" name="username" id="username" value="{{ current_user.username }}" disabled class="form-input bg-gray-100 dark:bg-gray-700">
</div>
<div>
<label for="full_name" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Full Name</label>
<input type="text" name="full_name" id="full_name" value="{{ current_user.full_name or '' }}" class="form-input">
</div>
<div>
<label for="preferred_language" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Language</label>
<select name="preferred_language" id="preferred_language" class="form-input">
{% for code, label in config['LANGUAGES'].items() %}
<option value="{{ code }}" {% if (current_user.preferred_language or current_language_code) == code %}selected{% endif %}>{{ label }}</option>
{% endfor %}
</select>
</div>
<div>
<label for="password" class="block text-sm font-medium text-gray-700 dark:text-gray-300">New Password</label>
<input type="password" name="password" id="password" class="form-input" placeholder="Leave blank to keep current password">
</div>
<div>
<label for="password_confirm" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Confirm New Password</label>
<input type="password" name="password_confirm" id="password_confirm" class="form-input">
</div>
</div>
<div class="mt-8 border-t border-border-light dark:border-border-dark pt-6 flex justify-end">
<a href="{{ url_for('auth.profile') }}" class="bg-gray-200 dark:bg-gray-700 px-4 py-2 rounded-lg mr-4">Cancel</a>
<button type="submit" class="bg-primary text-white px-4 py-2 rounded-lg">Save Changes</button>
</div>
</form>
</div>
<script>
function previewAvatar(input) {
const preview = document.getElementById('avatar-preview');
if (input.files && input.files[0]) {
const file = input.files[0];
// Validate file size (5MB limit)
if (file.size > 5 * 1024 * 1024) {
alert('File size must be less than 5MB');
input.value = '';
return;
}
// Validate file type
const allowedTypes = ['image/png', 'image/jpeg', 'image/jpg', 'image/gif', 'image/webp'];
if (!allowedTypes.includes(file.type)) {
alert('Invalid file type. Please select a valid image file (PNG, JPG, GIF, or WEBP).');
input.value = '';
return;
}
// Read and display the image
const reader = new FileReader();
reader.onload = function(e) {
preview.src = e.target.result;
};
reader.readAsDataURL(file);
}
}
</script>
{% endblock %}