feat: My Account — modifier username, nom, email, mot de passe
Backend:
- PUT /api/settings/account (full_name, login, email, password)
- Vérifie que le username n'est pas déjà pris
- Password min 6 caractères
Frontend (Settings → My Account):
- Champs éditables: Username, Full name, Email
- Section Change password avec toggle 👁/🙈
- Bouton Save changes + Update password
- Message de confirmation ✓ / ✗ avec timeout 3s
This commit is contained in:
@@ -150,17 +150,47 @@
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-row">
|
||||
<div>
|
||||
<div class="setting-label">Full name</div>
|
||||
<div class="setting-desc" x-text="userInfo.full_name || userInfo.login"></div>
|
||||
</div>
|
||||
|
||||
<!-- Editable fields -->
|
||||
<div class="setting-group">
|
||||
<h3>Account details</h3>
|
||||
<div class="setting-row" style="flex-direction:column;align-items:stretch;gap:4px;">
|
||||
<div class="setting-label">Username</div>
|
||||
<input type="text" class="settings-input" x-model="accountForm.login" style="max-width:300px;">
|
||||
</div>
|
||||
<div class="setting-row" style="flex-direction:column;align-items:stretch;gap:4px;">
|
||||
<div class="setting-label">Full name</div>
|
||||
<input type="text" class="settings-input" x-model="accountForm.full_name" style="max-width:300px;">
|
||||
</div>
|
||||
<div class="setting-row" style="flex-direction:column;align-items:stretch;gap:4px;">
|
||||
<div class="setting-label">Email</div>
|
||||
<input type="email" class="settings-input" x-model="accountForm.email" style="max-width:300px;">
|
||||
</div>
|
||||
<div style="margin-top:12px;">
|
||||
<button class="btn btn-primary" @click="saveAccount()" :disabled="accountSaving" style="width:auto;padding:8px 20px;font-size:13px;">
|
||||
<span x-show="!accountSaving">💾 Save changes</span>
|
||||
<span x-show="accountSaving">Saving…</span>
|
||||
</button>
|
||||
<span x-show="accountMsg" x-text="accountMsg" style="margin-left:12px;font-size:13px;color:#0F7B6C;"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Password change -->
|
||||
<div class="setting-group">
|
||||
<h3>Change password</h3>
|
||||
<div class="setting-row" style="flex-direction:column;align-items:stretch;gap:4px;">
|
||||
<div class="setting-label">New password</div>
|
||||
<div class="pw-wrapper" style="max-width:300px;">
|
||||
<input :type="pwVisible ? 'text' : 'password'" class="settings-input" x-model="accountForm.password" placeholder="Leave blank to keep current" style="padding-right:36px;">
|
||||
<button type="button" class="pw-toggle" @click="pwVisible=!pwVisible" x-text="pwVisible ? '🙈' : '👁'" style="top:50%;"></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-row">
|
||||
<div>
|
||||
<div class="setting-label">Email</div>
|
||||
<div class="setting-desc" x-text="userInfo.email || '—'"></div>
|
||||
</div>
|
||||
<div style="margin-top:12px;">
|
||||
<button class="btn btn-primary" @click="saveAccount()" :disabled="accountSaving || !accountForm.password" style="width:auto;padding:8px 20px;font-size:13px;">
|
||||
<span x-show="!accountSaving">🔒 Update password</span>
|
||||
<span x-show="accountSaving">Saving…</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -472,6 +502,11 @@ document.addEventListener('alpine:init', function() {
|
||||
defaultView: localStorage.getItem('fd_default_view') || 'tree',
|
||||
userInfo: {full_name: '{{ user.full_name or "" }}', login: '{{ user.login or "" }}', email: '{{ user.email or "" }}'},
|
||||
userIsAdmin: {{ 'true' if user.get('is_admin') else 'false' }},
|
||||
// Account form
|
||||
accountForm: {login:'{{ user.login or "" }}', full_name:'{{ user.full_name or "" }}', email:'{{ user.email or "" }}', password:''},
|
||||
accountSaving: false,
|
||||
accountMsg: '',
|
||||
pwVisible: false,
|
||||
// Admin data
|
||||
adminUsers: [],
|
||||
adminStats: {},
|
||||
@@ -628,6 +663,34 @@ document.addEventListener('alpine:init', function() {
|
||||
} catch(e) {}
|
||||
},
|
||||
|
||||
async saveAccount() {
|
||||
this.accountSaving = true;
|
||||
this.accountMsg = '';
|
||||
var body = {};
|
||||
var f = this.accountForm;
|
||||
if (f.full_name !== this.userInfo.full_name) body.full_name = f.full_name;
|
||||
if (f.login !== this.userInfo.login) body.login = f.login;
|
||||
if (f.email !== this.userInfo.email) body.email = f.email;
|
||||
if (f.password) body.password = f.password;
|
||||
if (Object.keys(body).length === 0) { this.accountSaving = false; return; }
|
||||
try {
|
||||
var r = await fetch('/api/settings/account', {
|
||||
method: 'PUT', headers: {'Content-Type':'application/json', 'X-CSRF-Token': this.getCsrfToken()},
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
var d = await r.json();
|
||||
if (r.ok) {
|
||||
this.userInfo = {login: d.user.login, full_name: d.user.full_name, email: d.user.email};
|
||||
this.accountForm.password = '';
|
||||
this.accountMsg = '✓ Saved!';
|
||||
setTimeout(() => { this.accountMsg = ''; }, 3000);
|
||||
} else {
|
||||
this.accountMsg = '✗ ' + (d.error || 'Error');
|
||||
}
|
||||
} catch(e) { this.accountMsg = '✗ Network error'; }
|
||||
this.accountSaving = false;
|
||||
},
|
||||
|
||||
async uploadAvatar(ev) {
|
||||
var file = ev.target.files[0];
|
||||
if (!file) return;
|
||||
|
||||
Reference in New Issue
Block a user