feat: make admin user edit page fields editable (username, email, name)
Some checks failed
Some checks failed
The admin-user-edit page had display-only fields for username, email, first name, and last name. Convert to editable form inputs with: - Dirty detection (unsaved changes indicator) - Only sends changed fields in PUT payload - Validation error display per field - Save button disabled when no changes Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -18,6 +18,14 @@ function adminUserEditPage() {
|
|||||||
userId: null,
|
userId: null,
|
||||||
currentUserId: null,
|
currentUserId: null,
|
||||||
|
|
||||||
|
// Editable profile form
|
||||||
|
editForm: {
|
||||||
|
username: '',
|
||||||
|
email: '',
|
||||||
|
first_name: '',
|
||||||
|
last_name: ''
|
||||||
|
},
|
||||||
|
|
||||||
// Platform assignment state
|
// Platform assignment state
|
||||||
showPlatformModal: false,
|
showPlatformModal: false,
|
||||||
availablePlatforms: [],
|
availablePlatforms: [],
|
||||||
@@ -98,6 +106,14 @@ function adminUserEditPage() {
|
|||||||
full_name: [response.first_name, response.last_name].filter(Boolean).join(' ') || null
|
full_name: [response.first_name, response.last_name].filter(Boolean).join(' ') || null
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Populate editable form
|
||||||
|
this.editForm = {
|
||||||
|
username: this.adminUser.username || '',
|
||||||
|
email: this.adminUser.email || '',
|
||||||
|
first_name: this.adminUser.first_name || '',
|
||||||
|
last_name: this.adminUser.last_name || ''
|
||||||
|
};
|
||||||
|
|
||||||
adminUserEditLog.info(`Admin user loaded in ${duration}ms`, {
|
adminUserEditLog.info(`Admin user loaded in ${duration}ms`, {
|
||||||
id: this.adminUser.id,
|
id: this.adminUser.id,
|
||||||
username: this.adminUser.username,
|
username: this.adminUser.username,
|
||||||
@@ -141,6 +157,78 @@ function adminUserEditPage() {
|
|||||||
return Utils.formatDate(dateString);
|
return Utils.formatDate(dateString);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Check if profile form has unsaved changes
|
||||||
|
get profileDirty() {
|
||||||
|
if (!this.adminUser) return false;
|
||||||
|
return this.editForm.username !== (this.adminUser.username || '') ||
|
||||||
|
this.editForm.email !== (this.adminUser.email || '') ||
|
||||||
|
this.editForm.first_name !== (this.adminUser.first_name || '') ||
|
||||||
|
this.editForm.last_name !== (this.adminUser.last_name || '');
|
||||||
|
},
|
||||||
|
|
||||||
|
// Save profile changes
|
||||||
|
async saveProfile() {
|
||||||
|
adminUserEditLog.info('Saving profile changes...');
|
||||||
|
this.errors = {};
|
||||||
|
|
||||||
|
// Build update payload with only changed fields
|
||||||
|
const payload = {};
|
||||||
|
if (this.editForm.username !== (this.adminUser.username || '')) {
|
||||||
|
payload.username = this.editForm.username;
|
||||||
|
}
|
||||||
|
if (this.editForm.email !== (this.adminUser.email || '')) {
|
||||||
|
payload.email = this.editForm.email;
|
||||||
|
}
|
||||||
|
if (this.editForm.first_name !== (this.adminUser.first_name || '')) {
|
||||||
|
payload.first_name = this.editForm.first_name;
|
||||||
|
}
|
||||||
|
if (this.editForm.last_name !== (this.adminUser.last_name || '')) {
|
||||||
|
payload.last_name = this.editForm.last_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(payload).length === 0) return;
|
||||||
|
|
||||||
|
this.saving = true;
|
||||||
|
try {
|
||||||
|
const url = `/admin/users/${this.userId}`;
|
||||||
|
window.LogConfig.logApiCall('PUT', url, payload, 'request');
|
||||||
|
|
||||||
|
const response = await apiClient.put(url, payload);
|
||||||
|
|
||||||
|
window.LogConfig.logApiCall('PUT', url, response, 'response');
|
||||||
|
|
||||||
|
// Update local state
|
||||||
|
this.adminUser.username = response.username;
|
||||||
|
this.adminUser.email = response.email;
|
||||||
|
this.adminUser.first_name = response.first_name;
|
||||||
|
this.adminUser.last_name = response.last_name;
|
||||||
|
this.adminUser.full_name = [response.first_name, response.last_name].filter(Boolean).join(' ') || null;
|
||||||
|
|
||||||
|
// Re-sync form
|
||||||
|
this.editForm = {
|
||||||
|
username: response.username || '',
|
||||||
|
email: response.email || '',
|
||||||
|
first_name: response.first_name || '',
|
||||||
|
last_name: response.last_name || ''
|
||||||
|
};
|
||||||
|
|
||||||
|
Utils.showToast('Profile updated successfully', 'success');
|
||||||
|
adminUserEditLog.info('Profile updated successfully');
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
window.LogConfig.logError(error, 'Save Profile');
|
||||||
|
if (error.details) {
|
||||||
|
for (const detail of error.details) {
|
||||||
|
const field = detail.loc?.[detail.loc.length - 1];
|
||||||
|
if (field) this.errors[field] = detail.msg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Utils.showToast(error.message || 'Failed to save profile', 'error');
|
||||||
|
} finally {
|
||||||
|
this.saving = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
// Toggle super admin status
|
// Toggle super admin status
|
||||||
async toggleSuperAdmin() {
|
async toggleSuperAdmin() {
|
||||||
const isSuperAdmin = this.adminUser.role === 'super_admin';
|
const isSuperAdmin = this.adminUser.role === 'super_admin';
|
||||||
|
|||||||
@@ -71,39 +71,76 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Admin Info Card -->
|
<!-- Admin Info Card (Editable) -->
|
||||||
<div class="px-4 py-3 mb-6 bg-white rounded-lg shadow-md dark:bg-gray-800">
|
<div class="px-4 py-3 mb-6 bg-white rounded-lg shadow-md dark:bg-gray-800">
|
||||||
<h3 class="mb-4 text-lg font-semibold text-gray-700 dark:text-gray-200">
|
<div class="flex items-center justify-between mb-4">
|
||||||
Admin Information
|
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-200">
|
||||||
</h3>
|
Admin Information
|
||||||
<div class="grid gap-6 md:grid-cols-2">
|
</h3>
|
||||||
<!-- Left Column -->
|
<span class="text-xs text-gray-500 dark:text-gray-400" x-text="'ID: ' + adminUser?.id"></span>
|
||||||
<div class="space-y-4">
|
|
||||||
<div>
|
|
||||||
<span class="text-sm text-gray-600 dark:text-gray-400">User ID</span>
|
|
||||||
<p class="text-gray-800 dark:text-gray-200 font-medium" x-text="adminUser?.id"></p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span class="text-sm text-gray-600 dark:text-gray-400">Username</span>
|
|
||||||
<p class="text-gray-800 dark:text-gray-200 font-medium" x-text="adminUser?.username"></p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span class="text-sm text-gray-600 dark:text-gray-400">Email</span>
|
|
||||||
<p class="text-gray-800 dark:text-gray-200 font-medium" x-text="adminUser?.email"></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Right Column -->
|
|
||||||
<div class="space-y-4">
|
|
||||||
<div>
|
|
||||||
<span class="text-sm text-gray-600 dark:text-gray-400">First Name</span>
|
|
||||||
<p class="text-gray-800 dark:text-gray-200 font-medium" x-text="adminUser?.first_name || '-'"></p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span class="text-sm text-gray-600 dark:text-gray-400">Last Name</span>
|
|
||||||
<p class="text-gray-800 dark:text-gray-200 font-medium" x-text="adminUser?.last_name || '-'"></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<form @submit.prevent="saveProfile()">
|
||||||
|
<div class="grid gap-6 md:grid-cols-2">
|
||||||
|
<!-- Left Column -->
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-400 mb-1">Username</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
x-model="editForm.username"
|
||||||
|
class="block w-full text-sm dark:text-gray-300 dark:border-gray-600 dark:bg-gray-700 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple form-input"
|
||||||
|
required
|
||||||
|
minlength="3"
|
||||||
|
>
|
||||||
|
<p x-show="errors.username" x-text="errors.username" class="mt-1 text-xs text-red-600 dark:text-red-400"></p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-400 mb-1">Email</label>
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
x-model="editForm.email"
|
||||||
|
class="block w-full text-sm dark:text-gray-300 dark:border-gray-600 dark:bg-gray-700 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple form-input"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
<p x-show="errors.email" x-text="errors.email" class="mt-1 text-xs text-red-600 dark:text-red-400"></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Right Column -->
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-400 mb-1">First Name</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
x-model="editForm.first_name"
|
||||||
|
class="block w-full text-sm dark:text-gray-300 dark:border-gray-600 dark:bg-gray-700 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple form-input"
|
||||||
|
placeholder="First name"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-400 mb-1">Last Name</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
x-model="editForm.last_name"
|
||||||
|
class="block w-full text-sm dark:text-gray-300 dark:border-gray-600 dark:bg-gray-700 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple form-input"
|
||||||
|
placeholder="Last name"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center justify-end mt-6 gap-3">
|
||||||
|
<span x-show="profileDirty" class="text-xs text-orange-600 dark:text-orange-400">Unsaved changes</span>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
:disabled="saving || !profileDirty"
|
||||||
|
class="flex items-center px-4 py-2 text-sm font-medium text-white bg-purple-600 rounded-lg hover:bg-purple-700 focus:outline-none disabled:opacity-50 transition-colors">
|
||||||
|
<span x-show="!saving">Save Changes</span>
|
||||||
|
<span x-show="saving" class="flex items-center">
|
||||||
|
<span x-html="$icon('spinner', 'w-4 h-4 mr-2')"></span>
|
||||||
|
Saving...
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Platform Assignments Card (Only for Platform Admins) -->
|
<!-- Platform Assignments Card (Only for Platform Admins) -->
|
||||||
|
|||||||
Reference in New Issue
Block a user