-
-
Notifications
You must be signed in to change notification settings - Fork 0
feat(portal): implement RBAC with permission gates and user management #526
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,169 @@ | ||
| @using CloudHealthOffice.Portal.Services | ||
| @inject HttpClient Http | ||
| @inject IConfiguration Configuration | ||
|
|
||
| <MudDialog> | ||
| <TitleContent> | ||
| <MudText Typo="Typo.h6"> | ||
| <MudIcon Icon="@(IsEdit ? Icons.Material.Filled.Edit : Icons.Material.Filled.PersonAdd)" Class="mr-2" /> | ||
| @(IsEdit ? "Edit User" : "Add User") | ||
| </MudText> | ||
| </TitleContent> | ||
| <DialogContent> | ||
| <MudTextField @bind-Value="_email" Label="Email" Required="true" RequiredError="Email is required" | ||
| InputType="InputType.Email" Disabled="@IsEdit" Class="mb-3" /> | ||
| <MudTextField @bind-Value="_displayName" Label="Display Name" Required="true" | ||
| RequiredError="Display name is required" Class="mb-3" /> | ||
| <MudStack Row="true" Spacing="2" Class="mb-3"> | ||
| <MudTextField @bind-Value="_firstName" Label="First Name" /> | ||
| <MudTextField @bind-Value="_lastName" Label="Last Name" /> | ||
| </MudStack> | ||
| <MudSelect T="string" Label="Department" @bind-Value="_department" Class="mb-3"> | ||
| <MudSelectItem Value="@("Claims")">Claims</MudSelectItem> | ||
| <MudSelectItem Value="@("Utilization Management")">Utilization Management</MudSelectItem> | ||
| <MudSelectItem Value="@("Member Services")">Member Services</MudSelectItem> | ||
| <MudSelectItem Value="@("Enrollment")">Enrollment</MudSelectItem> | ||
| <MudSelectItem Value="@("Provider Relations")">Provider Relations</MudSelectItem> | ||
| <MudSelectItem Value="@("Finance")">Finance</MudSelectItem> | ||
| <MudSelectItem Value="@("Compliance")">Compliance</MudSelectItem> | ||
| <MudSelectItem Value="@("Administration")">Administration</MudSelectItem> | ||
| </MudSelect> | ||
| <MudSelect T="string" Label="Roles" MultiSelection="true" @bind-SelectedValues="_selectedRoles" Class="mb-3"> | ||
| @foreach (var role in AvailableRoles) | ||
| { | ||
| <MudSelectItem T="string" Value="@role">@FormatRoleName(role)</MudSelectItem> | ||
| } | ||
| </MudSelect> | ||
|
|
||
| @if (!string.IsNullOrEmpty(_errorMessage)) | ||
| { | ||
| <MudAlert Severity="Severity.Error" Class="mt-2">@_errorMessage</MudAlert> | ||
| } | ||
| </DialogContent> | ||
| <DialogActions> | ||
| <MudButton OnClick="Cancel">Cancel</MudButton> | ||
| <MudButton Color="Color.Primary" Variant="Variant.Filled" OnClick="Submit" Disabled="_saving"> | ||
| @if (_saving) { <MudProgressCircular Size="Size.Small" Indeterminate="true" Class="mr-2" /> } | ||
| @(IsEdit ? "Save Changes" : "Add User") | ||
| </MudButton> | ||
| </DialogActions> | ||
| </MudDialog> | ||
|
|
||
| @code { | ||
| [CascadingParameter] IMudDialogInstance MudDialog { get; set; } = null!; | ||
|
Check failure on line 53 in src/portal/CloudHealthOffice.Portal/Pages/AddEditUserDialog.razor
|
||
| [Parameter] public string? TenantId { get; set; } | ||
| [Parameter] public UserManagement.UserListItem? ExistingUser { get; set; } | ||
| [Parameter] public List<string> AvailableRoles { get; set; } = new(); | ||
|
|
||
| private string _email = string.Empty; | ||
| private string _displayName = string.Empty; | ||
| private string _firstName = string.Empty; | ||
| private string _lastName = string.Empty; | ||
| private string _department = string.Empty; | ||
| private IEnumerable<string> _selectedRoles = new List<string>(); | ||
| private string? _errorMessage; | ||
| private bool _saving; | ||
|
|
||
| private bool IsEdit => ExistingUser != null; | ||
|
|
||
| protected override void OnInitialized() | ||
| { | ||
| if (ExistingUser != null) | ||
| { | ||
| _email = ExistingUser.Email; | ||
| _displayName = ExistingUser.DisplayName; | ||
| _department = ExistingUser.Department; | ||
|
Comment on lines
+71
to
+77
|
||
| _selectedRoles = ExistingUser.Roles; | ||
| } | ||
| } | ||
|
|
||
| private async Task Submit() | ||
| { | ||
| _errorMessage = null; | ||
|
|
||
| if (string.IsNullOrWhiteSpace(_email) || string.IsNullOrWhiteSpace(_displayName)) | ||
| { | ||
| _errorMessage = "Email and Display Name are required."; | ||
| return; | ||
| } | ||
|
|
||
| if (!_selectedRoles.Any()) | ||
| { | ||
| _errorMessage = "At least one role must be selected."; | ||
| return; | ||
| } | ||
|
|
||
| _saving = true; | ||
|
|
||
| try | ||
| { | ||
| var baseUrl = Configuration["Services:TenantService"] ?? "http://tenant-service.cho-svcs/api"; | ||
|
|
||
| if (IsEdit) | ||
| { | ||
| var response = await Http.PutAsJsonAsync( | ||
| $"{baseUrl}/v1/tenants/{TenantId}/users/{ExistingUser!.Id}", | ||
|
Comment on lines
+112
to
+113
|
||
| new | ||
| { | ||
| DisplayName = _displayName, | ||
| FirstName = _firstName, | ||
| LastName = _lastName, | ||
| Roles = _selectedRoles.ToList(), | ||
| Department = _department | ||
|
Comment on lines
+116
to
+120
|
||
| }); | ||
|
|
||
| if (!response.IsSuccessStatusCode) | ||
| { | ||
| _errorMessage = $"Failed to update user: {response.ReasonPhrase}"; | ||
| return; | ||
| } | ||
| } | ||
| else | ||
| { | ||
| var response = await Http.PostAsJsonAsync( | ||
| $"{baseUrl}/v1/tenants/{TenantId}/users", | ||
| new | ||
| { | ||
| Email = _email, | ||
| DisplayName = _displayName, | ||
| FirstName = _firstName, | ||
| LastName = _lastName, | ||
| Roles = _selectedRoles.ToList(), | ||
| Department = _department | ||
| }); | ||
|
|
||
| if (!response.IsSuccessStatusCode) | ||
| { | ||
| var body = await response.Content.ReadAsStringAsync(); | ||
| _errorMessage = $"Failed to add user: {body}"; | ||
| return; | ||
| } | ||
| } | ||
|
|
||
| MudDialog.Close(DialogResult.Ok(true)); | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| _errorMessage = $"Error: {ex.Message}"; | ||
| } | ||
| finally | ||
| { | ||
| _saving = false; | ||
| } | ||
| } | ||
|
|
||
| private void Cancel() => MudDialog.Cancel(); | ||
|
|
||
| private string FormatRoleName(string role) => role switch | ||
| { | ||
| "ClaimsExaminer" => "Claims Examiner", | ||
| "ClaimsSupervisor" => "Claims Supervisor", | ||
| "MemberServices" => "Member Services", | ||
| "EnrollmentSpecialist" => "Enrollment Specialist", | ||
| "UMCoordinator" => "UM Coordinator", | ||
| "ProviderRelations" => "Provider Relations", | ||
| "ComplianceOfficer" => "Compliance Officer", | ||
| "TenantAdmin" => "Tenant Admin", | ||
| _ => role | ||
| }; | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ExistingUseris typed asUserManagement.UserListItem, which couples this dialog to theUserManagementpage component and makes reuse/testing harder. Consider movingUserListIteminto a shared model/DTO class (e.g., underServices/orModels/) and using that type in both components.