@page "/users" @using Microsoft.AspNetCore.Authorization @rendermode @(new InteractiveServerRenderMode(prerender: false)) @attribute [Microsoft.AspNetCore.Authorization.Authorize] @inject ApiClientService ApiClient @inject IJSRuntime JS @using Microsoft.AspNetCore.Components.Authorization @inject AuthenticationStateProvider AuthStateProvider @inject MenuAuthorizationService MenuAuthorization @AppLocalization.PageTitle("users", "Users")

@AppLocalization.PageTitle("users", "Users")

@AppLocalization.PageDescription("users", "Manage people, status, and account details.")

@if (canCreateUser) { }
@if (isLoading) { } else {
@if (PagedUsers.Any()) { @foreach (var entry in PagedUsers.Select((user, index) => (user, index))) { var user = entry.user; } } else { }
No. @AppLocalization.Text("common.user", "User") Email Phone @AppLocalization.Text("common.role", "Role") @AppLocalization.Text("common.status", "Status") @AppLocalization.Text("common.actions", "Actions")
@((currentPage - 1) * pageSize + entry.index + 1)
@(user.FirstName.Length > 0 ? user.FirstName[0].ToString().ToUpper() : "")@(user.LastName.Length > 0 ? user.LastName[0].ToString().ToUpper() : "")

@user.FirstName @user.LastName

@@@user.Username

@user.Email @(user.Phone ?? "-") @{ var role = roles.FirstOrDefault(r => r.Id == user.RoleId); } @(role?.Name ?? $"Role #{user.RoleId}") @(user.IsActive ? AppLocalization.Text("status.active", "Active") : AppLocalization.Text("status.inactive", "Inactive"))
@if (canEditUser) { } @if (canDeleteUser) { }
}
@* ===== Create / Edit User Modal ===== *@ @if (showModal) {

@(isEditing ? AppLocalization.Text("user.editUser", "Edit User") : AppLocalization.Text("common.newUser", "New User"))

@if (isFirstNameError) { }
@if (isFirstNameError) {

@firstNameErrorMsg

}
@if (isLastNameError) { }
@if (isLastNameError) {

@lastNameErrorMsg

}
@if (isUsernameError) { }
@if (isUsernameError) {

@usernameErrorMsg

}
@if (isEmailError) { }
@if (isEmailError) {

@emailErrorMsg

}
@if (!isEditing) {
@if (isPasswordError) {

@passwordErrorMsg

}
}
@if (isEditing) {
} @if (!string.IsNullOrEmpty(errorMessage)) {

@errorMessage

}
} @* ===== Confirm Dialog ===== *@ @code { private bool isLoading = true; private bool showModal = false; private bool isEditing = false; private bool isSaving = false; private bool canCreateUser = false; private bool canEditUser = false; private bool canDeleteUser = false; private string? errorMessage; // Per-field validation errors private bool isFirstNameError = false; private string firstNameErrorMsg = ""; private bool isLastNameError = false; private string lastNameErrorMsg = ""; private bool isUsernameError = false; private string usernameErrorMsg = ""; private bool isEmailError = false; private string emailErrorMsg = ""; private bool isPasswordError = false; private string passwordErrorMsg = ""; // Confirm dialog state private bool showConfirmDialog = false; private string confirmTitle = ""; private string confirmMessage = ""; private string confirmButtonText = ""; private string confirmIcon = ""; private string confirmIconBg = ""; private string confirmIconText = ""; private string confirmButtonClass = ""; private Func? confirmAction; private List users = new(); private List roles = new(); private string? successMessage; private bool showPassword = false; // Search / filter private string searchInput = ""; private long filterRoleIdInput = 0; private string filterStatusInput = "all"; // Pagination state private int currentPage = 1; private int pageSize = 10; private int totalCount = 0; private int totalPages = 0; private int TotalPages => totalPages; private int TotalCount => totalCount; private IEnumerable PagedUsers => users; // Form fields private long editingId; private string formUsername = ""; private string formFirstName = ""; private string formLastName = ""; private string formEmail = ""; private string formPassword = ""; private string? formPhone; private long formRoleId; private bool formIsActive = true; protected override async Task OnInitializedAsync() { var authState = await AuthStateProvider.GetAuthenticationStateAsync(); var user = authState.User; canCreateUser = await MenuAuthorization.HasAccessCodeAsync(user, "Users_Create"); canEditUser = await MenuAuthorization.HasAccessCodeAsync(user, "Users_Update"); canDeleteUser = await MenuAuthorization.HasAccessCodeAsync(user, "Users_Delete"); var client = ApiClient.CreateClient(); try { var usersTask = LoadUsersPageAsync(client); var rolesTask = client.GetFromJsonAsync>("Role", Serialization.CaseInsensitive); await System.Threading.Tasks.Task.WhenAll(usersTask, rolesTask); users = usersTask.Result?.Items ?? new(); totalCount = usersTask.Result?.TotalCount ?? 0; totalPages = usersTask.Result?.TotalPages ?? 0; currentPage = usersTask.Result?.Page ?? currentPage; pageSize = usersTask.Result?.PageSize ?? pageSize; roles = rolesTask.Result ?? new(); } catch (Exception ex) { Console.WriteLine($"Load error: {ex.Message}"); } finally { isLoading = false; } } protected override async Task OnAfterRenderAsync(bool firstRender) { await JS.InvokeVoidAsync("initIcons"); } private async Task?> LoadUsersPageAsync(HttpClient? client = null) { client ??= ApiClient.CreateClient(); return await client.GetFromJsonAsync>(BuildUsersUrl(), Serialization.CaseInsensitive); } // Pagination handlers private async Task HandlePageChanged(int page) { currentPage = page; await LoadUsers(); } private async Task HandlePageSizeChanged(int size) { pageSize = size; currentPage = 1; await LoadUsers(); } private async Task ApplySearch() { currentPage = 1; await LoadUsers(); } private async Task ResetSearch() { searchInput = ""; filterRoleIdInput = 0; filterStatusInput = "all"; currentPage = 1; await LoadUsers(); } private async Task LoadUsers() { var client = ApiClient.CreateClient(); var result = await LoadUsersPageAsync(client); users = result?.Items ?? new(); totalCount = result?.TotalCount ?? 0; totalPages = result?.TotalPages ?? 0; currentPage = result?.Page ?? currentPage; pageSize = result?.PageSize ?? pageSize; } private string BuildUsersUrl() { var query = new List { $"page={currentPage}", $"limit={pageSize}" }; if (!string.IsNullOrWhiteSpace(searchInput)) { query.Add($"search={Uri.EscapeDataString(searchInput.Trim())}"); } if (filterRoleIdInput > 0) { query.Add($"roleId={filterRoleIdInput}"); } if (!string.Equals(filterStatusInput, "all", StringComparison.OrdinalIgnoreCase)) { query.Add($"status={Uri.EscapeDataString(filterStatusInput)}"); } return $"User?{string.Join("&", query)}"; } private void OpenCreateModal() { isEditing = false; editingId = 0; formUsername = ""; formFirstName = ""; formLastName = ""; formEmail = ""; formPassword = ""; formPhone = null; formRoleId = roles.FirstOrDefault()?.Id ?? 0; formIsActive = true; errorMessage = null; isFirstNameError = false; firstNameErrorMsg = ""; isLastNameError = false; lastNameErrorMsg = ""; isUsernameError = false; usernameErrorMsg = ""; isEmailError = false; emailErrorMsg = ""; isPasswordError = false; passwordErrorMsg = ""; showModal = true; } private void OpenEditModal(UserDto user) { isEditing = true; editingId = user.Id; formUsername = user.Username; formFirstName = user.FirstName; formLastName = user.LastName; formEmail = user.Email; formPhone = user.Phone; formRoleId = user.RoleId; formIsActive = user.IsActive; errorMessage = null; isFirstNameError = false; firstNameErrorMsg = ""; isLastNameError = false; lastNameErrorMsg = ""; isUsernameError = false; usernameErrorMsg = ""; isEmailError = false; emailErrorMsg = ""; isPasswordError = false; passwordErrorMsg = ""; showModal = true; } private void CloseModal() { showModal = false; errorMessage = null; isFirstNameError = false; isLastNameError = false; isUsernameError = false; isEmailError = false; isPasswordError = false; } private void RequestSaveConfirmation() { isFirstNameError = false; firstNameErrorMsg = ""; isLastNameError = false; lastNameErrorMsg = ""; isUsernameError = false; usernameErrorMsg = ""; isEmailError = false; emailErrorMsg = ""; isPasswordError = false; passwordErrorMsg = ""; bool hasError = false; if (string.IsNullOrWhiteSpace(formFirstName)) { isFirstNameError = true; firstNameErrorMsg = AppLocalization.Text("validation.firstNameRequired", "First name is required."); hasError = true; } if (string.IsNullOrWhiteSpace(formLastName)) { isLastNameError = true; lastNameErrorMsg = AppLocalization.Text("validation.lastNameRequired", "Last name is required."); hasError = true; } if (string.IsNullOrWhiteSpace(formUsername)) { isUsernameError = true; usernameErrorMsg = AppLocalization.Text("validation.usernameRequired", "Username is required."); hasError = true; } else if (formUsername.Length < 3) { isUsernameError = true; usernameErrorMsg = ResultMessages.UsernameMinLength; hasError = true; } else if (!System.Text.RegularExpressions.Regex.IsMatch(formUsername, @"^[a-zA-Z0-9._]+$")) { isUsernameError = true; usernameErrorMsg = ResultMessages.UsernameInvalidCharacters; hasError = true; } if (string.IsNullOrWhiteSpace(formEmail)) { isEmailError = true; emailErrorMsg = AppLocalization.Text("validation.emailRequired", "Email is required."); hasError = true; } if (!isEditing) { if (string.IsNullOrWhiteSpace(formPassword)) { isPasswordError = true; passwordErrorMsg = AppLocalization.Text("validation.passwordRequired", "Password is required."); hasError = true; } else if (formPassword.Length < 8) { isPasswordError = true; passwordErrorMsg = ResultMessages.PasswordMinLengthRule; hasError = true; } else if (!System.Text.RegularExpressions.Regex.IsMatch(formPassword, @"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^a-zA-Z0-9]).+$")) { isPasswordError = true; passwordErrorMsg = ResultMessages.PasswordComplexityRule; hasError = true; } } if (hasError) return; if (isEditing) { confirmTitle = AppLocalization.Text("user.updateUser", "Update User?"); confirmMessage = AppLocalization.Text("user.updateUserConfirm", "Are you sure you want to update this user?"); confirmButtonText = AppLocalization.Text("action.update", "Update"); confirmIcon = "pencil"; confirmIconBg = "bg-amber-50"; confirmIconText = "text-amber-500"; confirmButtonClass = "bg-violet-600 hover:bg-violet-700"; } else { confirmTitle = AppLocalization.Text("user.createUser", "Create User?"); confirmMessage = AppLocalization.Text("user.createUserConfirm", "Are you sure you want to create this user?"); confirmButtonText = AppLocalization.Text("action.create", "Create"); confirmIcon = "plus-circle"; confirmIconBg = "bg-emerald-50"; confirmIconText = "text-emerald-500"; confirmButtonClass = "bg-violet-600 hover:bg-violet-700"; } confirmAction = SaveUser; showConfirmDialog = true; } private void RequestDeleteConfirmation(long id) { confirmTitle = AppLocalization.Text("user.deleteUser", "Delete User?"); confirmMessage = AppLocalization.Text("user.deleteUserConfirm", "Are you sure you want to delete this user? This action cannot be undone."); confirmButtonText = AppLocalization.Text("action.delete", "Delete"); confirmIcon = "trash-2"; confirmIconBg = "bg-rose-50"; confirmIconText = "text-rose-500"; confirmButtonClass = "bg-violet-600 hover:bg-violet-700"; confirmAction = () => DeleteUser(id); showConfirmDialog = true; } private async Task HandleConfirmAction() { showConfirmDialog = false; if (confirmAction != null) await confirmAction(); } private void CloseConfirmDialog() { showConfirmDialog = false; } private async Task SaveUser() { if (string.IsNullOrWhiteSpace(formUsername) || string.IsNullOrWhiteSpace(formFirstName) || string.IsNullOrWhiteSpace(formLastName) || string.IsNullOrWhiteSpace(formEmail)) { errorMessage = ResultMessages.FillAllFields; return; } isSaving = true; errorMessage = null; var client = ApiClient.CreateClient(); try { if (isEditing) { var dto = new UpdateUserDto { Username = formUsername, FirstName = formFirstName, LastName = formLastName, Phone = formPhone, RoleId = formRoleId, IsActive = formIsActive }; var response = await client.PutAsJsonAsync($"User/{editingId}", dto); var res = await response.Content.ReadFromJsonAsync(Serialization.CaseInsensitive); if (res == null || !res.IsSuccess) { errorMessage = res?.ErrorMessage ?? ResultMessages.FailedToUpdateUser; return; } } else { if (string.IsNullOrWhiteSpace(formPassword)) { errorMessage = AppLocalization.Text("validation.passwordRequired", "Password is required."); isSaving = false; return; } if (formPassword.Length < 8) { errorMessage = ResultMessages.PasswordMinLengthRule; isSaving = false; return; } if (!System.Text.RegularExpressions.Regex.IsMatch(formPassword, @"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^a-zA-Z0-9]).+$")) { errorMessage = ResultMessages.PasswordComplexityRule; isSaving = false; return; } var dto = new CreateUserDto { Username = formUsername, FirstName = formFirstName, LastName = formLastName, Email = formEmail, Password = formPassword, Phone = formPhone, RoleId = formRoleId }; var response = await client.PostAsJsonAsync("User", dto); var res = await response.Content.ReadFromJsonAsync>(Serialization.CaseInsensitive); if (res == null || !res.IsSuccess) { errorMessage = res?.ErrorMessage ?? ResultMessages.FailedToCreateUser; return; } } CloseModal(); await LoadUsers(); successMessage = isEditing ? AppLocalization.Text("result.userUpdated", "User updated successfully!") : AppLocalization.Text("result.userCreated", "User created successfully!"); } catch (Exception ex) { errorMessage = ex.Message; } finally { isSaving = false; } } private async Task DeleteUser(long id) { var authState = await AuthStateProvider.GetAuthenticationStateAsync(); var currentUserIdStr = authState.User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value; if (currentUserIdStr != null && long.TryParse(currentUserIdStr, out var currentUserId) && currentUserId == id) { await JS.InvokeVoidAsync("alert", AppLocalization.Text("user.cannotDeleteSelf", "You cannot delete your own account.")); return; } var client = ApiClient.CreateClient(); try { var response = await client.DeleteAsync($"User/{id}"); var res = await response.Content.ReadFromJsonAsync(Serialization.CaseInsensitive); if (res == null || !res.IsSuccess) { await JS.InvokeVoidAsync("alert", res?.ErrorMessage ?? AppLocalization.Text("result.failedToDeleteUser", "Failed to delete user.")); return; } await LoadUsers(); if (currentPage > TotalPages && TotalPages > 0) currentPage = TotalPages; successMessage = AppLocalization.Text("result.userDeleted", "User deleted successfully!"); } catch (Exception ex) { Console.WriteLine($"Delete error: {ex.Message}"); } } private async Task TogglePassword() { showPassword = !showPassword; await JS.InvokeVoidAsync("initIcons"); } }