profile.html
250 lignes · 11180 octets
{% extends "base.html" %} {% block title %}{{ page_title }}{% endblock %} {% block navbar %} {% include "components/navbar.html" %} {% endblock %} {% block content %} <div class="container mx-auto px-4 py-8"> <!-- Breadcrumb --> <div class="text-sm breadcrumbs mb-6"> <ul> <li><a href="/">{{ breadcrumb_home }}</a></li> <li><a href="/profile">{{ breadcrumb_settings }}</a></li> <li>{{ breadcrumb_profile }}</li> </ul> </div> <!-- Page Header --> <div class="mb-8"> <h1 class="text-3xl font-bold mb-2">{{ title }}</h1> <p class="text-base-content/70">{{ subtitle }}</p> </div> <!-- Profile Form Card --> <div class="card bg-base-100 shadow-xl max-w-2xl"> <div class="card-body"> <h2 class="card-title mb-4">{{ account_info }}</h2> <form id="profileForm" class="space-y-6" data-saved="{{ profile_saved }}" data-error="{{ profile_error }}" data-network="{{ network_error }}" data-saving="{{ saving }}" data-save-label="{{ save_button }}"> <input type="hidden" name="csrf_token" value="{{ csrf_token }}"> <div class="form-control"> <label class="label" for="username"> <span class="label-text font-semibold">{{ username_label }}</span> <span class="label-text-alt text-warning">{{ username_public }}</span> </label> <input type="text" id="username" name="username" value="{{ username }}" placeholder="{{ username_placeholder }}" class="input input-bordered w-full" minlength="3" maxlength="39" pattern="[a-z0-9-]+" title="{{ username_pattern }}" required > <label class="label"> <span class="label-text-alt">{{ username_help }}</span> </label> </div> <div class="form-control"> <label class="label"> <span class="label-text font-semibold">{{ email_label }}</span> <span class="label-text-alt text-info">{{ email_used_for }}</span> </label> <input type="email" value="{{ email }}" class="input input-bordered w-full" readonly > <label class="label"> <span class="label-text-alt">{{ email_warning }}</span> </label> </div> <div class="card-actions justify-end"> <a href="/profile" class="btn btn-ghost">{{ cancel }}</a> <button type="submit" class="btn btn-primary">{{ save_button }}</button> </div> </form> </div> </div> <div class="card bg-base-100 shadow-xl max-w-2xl mt-6"> <div class="card-body"> <h2 class="card-title mb-4">{{ email_change_title }}</h2> <div class="alert alert-info mb-4"> <span>{{ email_change_info }}</span> </div> <div id="email-change-alert" class="hidden alert mb-4"> <span id="email-change-alert-text"></span> </div> <form id="emailChangeForm" class="space-y-4" data-error="{{ email_change_error }}" data-network="{{ network_error }}" data-saving="{{ saving }}" data-submit-label="{{ request_button }}"> <div class="form-control"> <label class="label" for="new-email"> <span class="label-text font-semibold">{{ new_email_label }}</span> </label> <input type="email" id="new-email" name="new_email" placeholder="{{ email_placeholder }}" class="input input-bordered w-full" required autocomplete="email" > </div> <div class="form-control"> <label class="label" for="current-password"> <span class="label-text font-semibold">{{ current_password_label }}</span> </label> <input type="password" id="current-password" name="current_password" class="input input-bordered w-full" required autocomplete="current-password" > </div> <div class="card-actions justify-end"> <button type="submit" class="btn btn-primary">{{ request_button }}</button> </div> </form> </div> </div> <!-- Additional Info Card --> <div class="card bg-base-200 shadow-xl max-w-2xl mt-6"> <div class="card-body"> <h3 class="card-title text-base">Informations supplémentaires</h3> <div class="grid grid-cols-2 gap-4 text-sm"> <div> <p class="text-base-content/70">{{ account_created }}</p> <p class="font-medium">{{ created_at }}</p> </div> <div> <p class="text-base-content/70">{{ roles_label }}</p> <p class="font-medium">{{ roles }}</p> </div> <div> <p class="text-base-content/70">ID Utilisateur</p> <p class="font-mono text-xs">{{ user_id }}</p> </div> </div> </div> </div> <!-- Quick Links --> <div class="mt-8 flex gap-4 flex-wrap"> <a href="/settings/password" class="btn btn-outline btn-secondary"> <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" /></svg> Changer mon mot de passe </a> <a href="/settings/sessions" class="btn btn-outline"> <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" /></svg> Gérer mes sessions </a> <a href="/settings/preferences" class="btn btn-outline"> <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21a4 4 0 01-4-4V5a2 2 0 012-2h4a2 2 0 012 2v12a4 4 0 01-4 4zm0 0h12a2 2 0 002-2v-4a2 2 0 00-2-2h-2.343M11 7.343l1.657-1.657a2 2 0 012.828 0l2.829 2.829a2 2 0 010 2.828l-8.486 8.485M7 17h.01" /></svg> Préférences d'affichage </a> <a href="/settings/security" class="btn btn-outline btn-warning"> <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" /></svg> Sécurité 2FA </a> </div> </div> <script> document.getElementById('profileForm').addEventListener('submit', async function(e) { e.preventDefault(); const submitBtn = this.querySelector('button[type="submit"]'); const username = document.getElementById('username').value; submitBtn.disabled = true; submitBtn.textContent = this.dataset.saving; try { const response = await fetch('/profile', { method: 'PUT', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', 'X-CSRF-Token': '{{ csrf_token }}', }, credentials: 'include', body: JSON.stringify({ username }) }); const result = await response.json().catch(() => ({})); if (response.ok) { alert(this.dataset.saved); window.location.reload(); } else { alert(result.error || result.message || this.dataset.error); submitBtn.disabled = false; submitBtn.textContent = this.dataset.saveLabel; } } catch (error) { alert(this.dataset.network); submitBtn.disabled = false; submitBtn.textContent = this.dataset.saveLabel; } }); document.getElementById('emailChangeForm').addEventListener('submit', async function(e) { e.preventDefault(); const submitBtn = this.querySelector('button[type="submit"]'); const alertBox = document.getElementById('email-change-alert'); const alertText = document.getElementById('email-change-alert-text'); submitBtn.disabled = true; submitBtn.textContent = this.dataset.saving; alertBox.classList.add('hidden'); try { const response = await fetch('/api/v1/auth/email-change', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', }, credentials: 'include', body: JSON.stringify({ new_email: document.getElementById('new-email').value, current_password: document.getElementById('current-password').value }) }); const result = await response.json().catch(() => ({})); alertBox.classList.remove('hidden', 'alert-success', 'alert-error'); if (response.ok) { alertBox.classList.add('alert-success'); alertText.textContent = result.message || ''; this.reset(); } else { alertBox.classList.add('alert-error'); alertText.textContent = result.error || result.message || this.dataset.error; } } catch (error) { alertBox.classList.remove('hidden', 'alert-success'); alertBox.classList.add('alert-error'); alertText.textContent = this.dataset.network; } submitBtn.disabled = false; submitBtn.textContent = this.dataset.submitLabel; }); </script> {% endblock %}
GitRust