diff --git a/application/account-management/Api/Endpoints/TenantEndpoints.cs b/application/account-management/Api/Endpoints/TenantEndpoints.cs
index 9fc220fa4..a678c87a6 100644
--- a/application/account-management/Api/Endpoints/TenantEndpoints.cs
+++ b/application/account-management/Api/Endpoints/TenantEndpoints.cs
@@ -19,7 +19,7 @@ public void MapEndpoints(IEndpointRouteBuilder routes)
).Produces();
group.MapPut("/current", async Task (UpdateCurrentTenantCommand command, IMediator mediator)
- => await mediator.Send(command)
+ => (await mediator.Send(command)).AddRefreshAuthenticationTokens()
);
routes.MapDelete("/internal-api/account-management/tenants/{id}", async Task (TenantId id, IMediator mediator)
diff --git a/application/account-management/Api/Endpoints/UserEndpoints.cs b/application/account-management/Api/Endpoints/UserEndpoints.cs
index 1dd1c1d8b..4634741a3 100644
--- a/application/account-management/Api/Endpoints/UserEndpoints.cs
+++ b/application/account-management/Api/Endpoints/UserEndpoints.cs
@@ -18,6 +18,10 @@ public void MapEndpoints(IEndpointRouteBuilder routes)
=> await mediator.Send(query)
).Produces();
+ group.MapGet("/{id}", async Task> (UserId id, IMediator mediator)
+ => await mediator.Send(new GetUserByIdQuery(id))
+ ).Produces();
+
group.MapGet("/summary", async Task> (IMediator mediator)
=> await mediator.Send(new GetUserSummaryQuery())
).Produces();
diff --git a/application/account-management/Core/Configuration.cs b/application/account-management/Core/Configuration.cs
index 4dfd2b52a..8ea37a419 100644
--- a/application/account-management/Core/Configuration.cs
+++ b/application/account-management/Core/Configuration.cs
@@ -30,6 +30,7 @@ public static IServiceCollection AddAccountManagementServices(this IServiceColle
return services
.AddSharedServices(Assembly)
- .AddScoped();
+ .AddScoped()
+ .AddScoped();
}
}
diff --git a/application/account-management/Core/Features/Authentication/Commands/CompleteLogin.cs b/application/account-management/Core/Features/Authentication/Commands/CompleteLogin.cs
index 4a338d30d..aa69c28a9 100644
--- a/application/account-management/Core/Features/Authentication/Commands/CompleteLogin.cs
+++ b/application/account-management/Core/Features/Authentication/Commands/CompleteLogin.cs
@@ -1,11 +1,9 @@
using JetBrains.Annotations;
-using Mapster;
using PlatformPlatform.AccountManagement.Features.Authentication.Domain;
using PlatformPlatform.AccountManagement.Features.EmailConfirmations.Commands;
using PlatformPlatform.AccountManagement.Features.Users.Domain;
using PlatformPlatform.AccountManagement.Features.Users.Shared;
using PlatformPlatform.AccountManagement.Integrations.Gravatar;
-using PlatformPlatform.SharedKernel.Authentication;
using PlatformPlatform.SharedKernel.Authentication.TokenGeneration;
using PlatformPlatform.SharedKernel.Cqrs;
using PlatformPlatform.SharedKernel.Telemetry;
@@ -22,6 +20,7 @@ public sealed record CompleteLoginCommand(string OneTimePassword) : ICommand, IR
public sealed class CompleteLoginHandler(
IUserRepository userRepository,
ILoginRepository loginRepository,
+ UserInfoFactory userInfoFactory,
AuthenticationTokenService authenticationTokenService,
IMediator mediator,
AvatarUpdater avatarUpdater,
@@ -75,7 +74,8 @@ public async Task Handle(CompleteLoginCommand command, CancellationToken
login.MarkAsCompleted();
loginRepository.Update(login);
- authenticationTokenService.CreateAndSetAuthenticationTokens(user.Adapt());
+ var userInfo = await userInfoFactory.CreateUserInfoAsync(user, cancellationToken);
+ authenticationTokenService.CreateAndSetAuthenticationTokens(userInfo);
events.CollectEvent(new LoginCompleted(user.Id, completeEmailConfirmationResult.Value!.ConfirmationTimeInSeconds));
diff --git a/application/account-management/Core/Features/Authentication/Commands/RefreshAuthenticationTokens.cs b/application/account-management/Core/Features/Authentication/Commands/RefreshAuthenticationTokens.cs
index 3b21c8742..5cfe2757c 100644
--- a/application/account-management/Core/Features/Authentication/Commands/RefreshAuthenticationTokens.cs
+++ b/application/account-management/Core/Features/Authentication/Commands/RefreshAuthenticationTokens.cs
@@ -1,10 +1,9 @@
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using JetBrains.Annotations;
-using Mapster;
using Microsoft.AspNetCore.Http;
using PlatformPlatform.AccountManagement.Features.Users.Domain;
-using PlatformPlatform.SharedKernel.Authentication;
+using PlatformPlatform.AccountManagement.Features.Users.Shared;
using PlatformPlatform.SharedKernel.Authentication.TokenGeneration;
using PlatformPlatform.SharedKernel.Cqrs;
using PlatformPlatform.SharedKernel.Domain;
@@ -17,6 +16,7 @@ public sealed record RefreshAuthenticationTokensCommand : ICommand, IRequest Handle(RefreshAuthenticationTokensCommand command, Can
// TODO: Check if the refreshTokenId exists in the database and if the jwtId and refreshTokenVersion are valid
- authenticationTokenService.RefreshAuthenticationTokens(user.Adapt(), refreshTokenId, refreshTokenVersion, refreshTokenExpires);
+ var userInfo = await userInfoFactory.CreateUserInfoAsync(user, cancellationToken);
+ authenticationTokenService.RefreshAuthenticationTokens(userInfo, refreshTokenId, refreshTokenVersion, refreshTokenExpires);
events.CollectEvent(new AuthenticationTokensRefreshed());
return Result.Success();
diff --git a/application/account-management/Core/Features/Signups/Commands/CompleteSignup.cs b/application/account-management/Core/Features/Signups/Commands/CompleteSignup.cs
index 697039e56..293544752 100644
--- a/application/account-management/Core/Features/Signups/Commands/CompleteSignup.cs
+++ b/application/account-management/Core/Features/Signups/Commands/CompleteSignup.cs
@@ -1,10 +1,9 @@
using JetBrains.Annotations;
-using Mapster;
using PlatformPlatform.AccountManagement.Features.EmailConfirmations.Commands;
using PlatformPlatform.AccountManagement.Features.EmailConfirmations.Domain;
using PlatformPlatform.AccountManagement.Features.Tenants.Commands;
using PlatformPlatform.AccountManagement.Features.Users.Domain;
-using PlatformPlatform.SharedKernel.Authentication;
+using PlatformPlatform.AccountManagement.Features.Users.Shared;
using PlatformPlatform.SharedKernel.Authentication.TokenGeneration;
using PlatformPlatform.SharedKernel.Cqrs;
using PlatformPlatform.SharedKernel.Telemetry;
@@ -20,6 +19,7 @@ public sealed record CompleteSignupCommand(string OneTimePassword, string Prefer
public sealed class CompleteSignupHandler(
IUserRepository userRepository,
+ UserInfoFactory userInfoFactory,
AuthenticationTokenService authenticationTokenService,
IMediator mediator,
ITelemetryEventsCollector events
@@ -42,7 +42,8 @@ public async Task Handle(CompleteSignupCommand command, CancellationToke
if (!createTenantResult.IsSuccess) return Result.From(createTenantResult);
var user = await userRepository.GetByIdAsync(createTenantResult.Value!.UserId, cancellationToken);
- authenticationTokenService.CreateAndSetAuthenticationTokens(user!.Adapt());
+ var userInfo = await userInfoFactory.CreateUserInfoAsync(user!, cancellationToken);
+ authenticationTokenService.CreateAndSetAuthenticationTokens(userInfo);
events.CollectEvent(
new SignupCompleted(createTenantResult.Value.TenantId, completeEmailConfirmationResult.Value!.ConfirmationTimeInSeconds)
diff --git a/application/account-management/Core/Features/Tenants/Commands/UpdateCurrentTenant.cs b/application/account-management/Core/Features/Tenants/Commands/UpdateCurrentTenant.cs
index 487ec7127..0e23a76d3 100644
--- a/application/account-management/Core/Features/Tenants/Commands/UpdateCurrentTenant.cs
+++ b/application/account-management/Core/Features/Tenants/Commands/UpdateCurrentTenant.cs
@@ -1,7 +1,9 @@
using FluentValidation;
using JetBrains.Annotations;
using PlatformPlatform.AccountManagement.Features.Tenants.Domain;
+using PlatformPlatform.AccountManagement.Features.Users.Domain;
using PlatformPlatform.SharedKernel.Cqrs;
+using PlatformPlatform.SharedKernel.ExecutionContext;
using PlatformPlatform.SharedKernel.Telemetry;
namespace PlatformPlatform.AccountManagement.Features.Tenants.Commands;
@@ -20,11 +22,19 @@ public UpdateCurrentTenantValidator()
}
}
-public sealed class UpdateTenantHandler(ITenantRepository tenantRepository, ITelemetryEventsCollector events)
- : IRequestHandler
+public sealed class UpdateTenantHandler(
+ ITenantRepository tenantRepository,
+ IExecutionContext executionContext,
+ ITelemetryEventsCollector events
+) : IRequestHandler
{
public async Task Handle(UpdateCurrentTenantCommand command, CancellationToken cancellationToken)
{
+ if (executionContext.UserInfo.Role != UserRole.Owner.ToString())
+ {
+ return Result.Forbidden("Only owners are allowed to update tenant information.");
+ }
+
var tenant = await tenantRepository.GetCurrentTenantAsync(cancellationToken);
tenant.Update(command.Name);
diff --git a/application/account-management/Core/Features/Users/Domain/UserRepository.cs b/application/account-management/Core/Features/Users/Domain/UserRepository.cs
index bd9dcf842..561d1a096 100644
--- a/application/account-management/Core/Features/Users/Domain/UserRepository.cs
+++ b/application/account-management/Core/Features/Users/Domain/UserRepository.cs
@@ -158,8 +158,16 @@ CancellationToken cancellationToken
? users.OrderBy(u => u.ModifiedAt)
: users.OrderByDescending(u => u.ModifiedAt),
SortableUserProperties.Name => sortOrder == SortOrder.Ascending
- ? users.OrderBy(u => u.FirstName).ThenBy(u => u.LastName)
- : users.OrderByDescending(u => u.FirstName).ThenByDescending(u => u.LastName),
+ ? users.OrderBy(u => u.FirstName == null ? 1 : 0)
+ .ThenBy(u => u.FirstName)
+ .ThenBy(u => u.LastName == null ? 1 : 0)
+ .ThenBy(u => u.LastName)
+ .ThenBy(u => u.Email)
+ : users.OrderBy(u => u.FirstName == null ? 0 : 1)
+ .ThenByDescending(u => u.FirstName)
+ .ThenBy(u => u.LastName == null ? 0 : 1)
+ .ThenByDescending(u => u.LastName)
+ .ThenBy(u => u.Email),
SortableUserProperties.Email => sortOrder == SortOrder.Ascending
? users.OrderBy(u => u.Email)
: users.OrderByDescending(u => u.Email),
@@ -167,6 +175,11 @@ CancellationToken cancellationToken
? users.OrderBy(u => u.Role)
: users.OrderByDescending(u => u.Role),
_ => users
+ .OrderBy(u => u.FirstName == null ? 1 : 0)
+ .ThenBy(u => u.FirstName)
+ .ThenBy(u => u.LastName == null ? 1 : 0)
+ .ThenBy(u => u.LastName)
+ .ThenBy(u => u.Email)
};
pageSize ??= 50;
diff --git a/application/account-management/Core/Features/Users/Queries/GetUserById.cs b/application/account-management/Core/Features/Users/Queries/GetUserById.cs
new file mode 100644
index 000000000..caf448d21
--- /dev/null
+++ b/application/account-management/Core/Features/Users/Queries/GetUserById.cs
@@ -0,0 +1,26 @@
+using JetBrains.Annotations;
+using Mapster;
+using PlatformPlatform.AccountManagement.Features.Users.Domain;
+using PlatformPlatform.SharedKernel.Cqrs;
+using PlatformPlatform.SharedKernel.Domain;
+
+namespace PlatformPlatform.AccountManagement.Features.Users.Queries;
+
+[PublicAPI]
+public sealed record GetUserByIdQuery(UserId Id) : IRequest>;
+
+public sealed class GetUserByIdHandler(IUserRepository userRepository)
+ : IRequestHandler>
+{
+ public async Task> Handle(GetUserByIdQuery query, CancellationToken cancellationToken)
+ {
+ var user = await userRepository.GetByIdAsync(query.Id, cancellationToken);
+
+ if (user is null)
+ {
+ return Result.NotFound($"User with ID '{query.Id}' not found.");
+ }
+
+ return user.Adapt();
+ }
+}
diff --git a/application/account-management/Core/Features/Users/Shared/UserInfoFactory.cs b/application/account-management/Core/Features/Users/Shared/UserInfoFactory.cs
new file mode 100644
index 000000000..46a78941f
--- /dev/null
+++ b/application/account-management/Core/Features/Users/Shared/UserInfoFactory.cs
@@ -0,0 +1,38 @@
+using PlatformPlatform.AccountManagement.Features.Tenants.Domain;
+using PlatformPlatform.AccountManagement.Features.Users.Domain;
+using PlatformPlatform.SharedKernel.Authentication;
+
+namespace PlatformPlatform.AccountManagement.Features.Users.Shared;
+
+///
+/// Factory for creating UserInfo instances with tenant information.
+/// Centralizes the logic for creating UserInfo to follow SRP and avoid duplication.
+///
+public sealed class UserInfoFactory(ITenantRepository tenantRepository)
+{
+ ///
+ /// Creates a UserInfo instance from a User entity, including tenant name.
+ ///
+ /// The user entity
+ /// Cancellation token
+ /// UserInfo with all required properties including tenant name
+ public async Task CreateUserInfoAsync(User user, CancellationToken cancellationToken)
+ {
+ var tenant = await tenantRepository.GetByIdAsync(user.TenantId, cancellationToken);
+
+ return new UserInfo
+ {
+ IsAuthenticated = true,
+ Id = user.Id,
+ TenantId = user.TenantId,
+ Role = user.Role.ToString(),
+ Email = user.Email,
+ FirstName = user.FirstName,
+ LastName = user.LastName,
+ Title = user.Title,
+ AvatarUrl = user.Avatar.Url,
+ TenantName = tenant?.Name,
+ Locale = user.Locale
+ };
+ }
+}
diff --git a/application/account-management/Tests/Tenants/UpdateCurrentTenantTests.cs b/application/account-management/Tests/Tenants/UpdateCurrentTenantTests.cs
index a34bc4681..7b9c4fd1e 100644
--- a/application/account-management/Tests/Tenants/UpdateCurrentTenantTests.cs
+++ b/application/account-management/Tests/Tenants/UpdateCurrentTenantTests.cs
@@ -47,4 +47,19 @@ public async Task UpdateCurrentTenant_WhenInvalid_ShouldReturnBadRequest()
TelemetryEventsCollectorSpy.AreAllEventsDispatched.Should().BeFalse();
}
+
+ [Fact]
+ public async Task UpdateCurrentTenant_WhenNonOwner_ShouldReturnForbidden()
+ {
+ // Arrange
+ var command = new UpdateCurrentTenantCommand { Name = Faker.TenantName() };
+
+ // Act
+ var response = await AuthenticatedMemberHttpClient.PutAsJsonAsync("/api/account-management/tenants/current", command);
+
+ // Assert
+ await response.ShouldHaveErrorStatusCode(HttpStatusCode.Forbidden, "Only owners are allowed to update tenant information.");
+
+ TelemetryEventsCollectorSpy.AreAllEventsDispatched.Should().BeFalse();
+ }
}
diff --git a/application/account-management/Tests/Users/GetUserByIdTests.cs b/application/account-management/Tests/Users/GetUserByIdTests.cs
new file mode 100644
index 000000000..a94150c45
--- /dev/null
+++ b/application/account-management/Tests/Users/GetUserByIdTests.cs
@@ -0,0 +1,85 @@
+using System.Net;
+using System.Text.Json;
+using FluentAssertions;
+using PlatformPlatform.AccountManagement.Database;
+using PlatformPlatform.AccountManagement.Features.Users.Domain;
+using PlatformPlatform.AccountManagement.Features.Users.Queries;
+using PlatformPlatform.SharedKernel.Domain;
+using PlatformPlatform.SharedKernel.Tests;
+using PlatformPlatform.SharedKernel.Tests.Persistence;
+using Xunit;
+
+namespace PlatformPlatform.AccountManagement.Tests.Users;
+
+public sealed class GetUserByIdTests : EndpointBaseTest
+{
+ private readonly UserId _userId = UserId.NewId();
+
+ public GetUserByIdTests()
+ {
+ Connection.Insert("Users", [
+ ("TenantId", DatabaseSeeder.Tenant1.Id.ToString()),
+ ("Id", _userId.ToString()),
+ ("CreatedAt", TimeProvider.System.GetUtcNow().AddMinutes(-10)),
+ ("ModifiedAt", null),
+ ("Email", Faker.Internet.Email()),
+ ("FirstName", Faker.Name.FirstName()),
+ ("LastName", Faker.Name.LastName()),
+ ("Title", Faker.Name.JobTitle()),
+ ("Role", UserRole.Member.ToString()),
+ ("EmailConfirmed", true),
+ ("Avatar", JsonSerializer.Serialize(new Avatar())),
+ ("Locale", "en-US")
+ ]
+ );
+ }
+
+ [Fact]
+ public async Task GetUserById_WhenUserExists_ShouldReturnUserDetails()
+ {
+ // Act
+ var response = await AuthenticatedOwnerHttpClient.GetAsync($"/api/account-management/users/{_userId}");
+
+ // Assert
+ response.ShouldBeSuccessfulGetRequest();
+ var userDetails = await response.DeserializeResponse();
+ userDetails.Should().NotBeNull();
+ userDetails.Id.Should().Be(_userId);
+ }
+
+ [Fact]
+ public async Task GetUserById_WhenUserDoesNotExist_ShouldReturnNotFound()
+ {
+ // Arrange
+ var nonExistentUserId = UserId.NewId();
+
+ // Act
+ var response = await AuthenticatedOwnerHttpClient.GetAsync($"/api/account-management/users/{nonExistentUserId}");
+
+ // Assert
+ await response.ShouldHaveErrorStatusCode(HttpStatusCode.NotFound, $"User with ID '{nonExistentUserId}' not found.");
+ }
+
+ [Fact]
+ public async Task GetUserById_WhenMemberTriesToAccessOtherUser_ShouldSucceed()
+ {
+ // Act
+ var response = await AuthenticatedMemberHttpClient.GetAsync($"/api/account-management/users/{_userId}");
+
+ // Assert
+ response.ShouldBeSuccessfulGetRequest();
+ var userDetails = await response.DeserializeResponse();
+ userDetails.Should().NotBeNull();
+ userDetails.Id.Should().Be(_userId);
+ }
+
+ [Fact]
+ public async Task GetUserById_WhenNotAuthenticated_ShouldReturnUnauthorized()
+ {
+ // Act
+ var response = await AnonymousHttpClient.GetAsync($"/api/account-management/users/{_userId}");
+
+ // Assert
+ response.StatusCode.Should().Be(HttpStatusCode.Unauthorized);
+ }
+}
diff --git a/application/account-management/WebApp/routes/admin/account/index.tsx b/application/account-management/WebApp/routes/admin/account/index.tsx
index cafbdff78..23aa21891 100644
--- a/application/account-management/WebApp/routes/admin/account/index.tsx
+++ b/application/account-management/WebApp/routes/admin/account/index.tsx
@@ -1,7 +1,7 @@
import { SharedSideMenu } from "@/shared/components/SharedSideMenu";
import { TopMenu } from "@/shared/components/topMenu";
import logoWrap from "@/shared/images/logo-wrap.svg";
-import { api } from "@/shared/lib/api/client";
+import { UserRole, api } from "@/shared/lib/api/client";
import { t } from "@lingui/core/macro";
import { Trans } from "@lingui/react/macro";
import { AppLayout } from "@repo/ui/components/AppLayout";
@@ -23,9 +23,12 @@ export const Route = createFileRoute("/admin/account/")({
export function AccountSettings() {
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
- const { data: tenant, isLoading } = api.useQuery("get", "/api/account-management/tenants/current");
+ const { data: tenant, isLoading: tenantLoading } = api.useQuery("get", "/api/account-management/tenants/current");
+ const { data: currentUser, isLoading: userLoading } = api.useQuery("get", "/api/account-management/users/me");
const updateCurrentTenantMutation = api.useMutation("put", "/api/account-management/tenants/current");
+ const isOwner = currentUser?.role === UserRole.Owner;
+
useEffect(() => {
if (updateCurrentTenantMutation.isSuccess) {
toastQueue.add({
@@ -36,7 +39,7 @@ export function AccountSettings() {
}
}, [updateCurrentTenantMutation.isSuccess]);
- if (isLoading) {
+ if (tenantLoading || userLoading) {
return null;
}
@@ -64,8 +67,8 @@ export function AccountSettings() {
-
-
- Danger zone
-
-
-
-
- Delete your account and all data. This action is irreversible—proceed with caution.
-
+ {isOwner && (
+
+
+ Danger zone
+
+
+
+
+ Delete your account and all data. This action is irreversible—proceed with caution.
+
-
+
+
-
+ )}
diff --git a/application/account-management/WebApp/routes/admin/users/-components/ChangeUserRoleDialog.tsx b/application/account-management/WebApp/routes/admin/users/-components/ChangeUserRoleDialog.tsx
index 3c15d022b..fbf60fc6f 100644
--- a/application/account-management/WebApp/routes/admin/users/-components/ChangeUserRoleDialog.tsx
+++ b/application/account-management/WebApp/routes/admin/users/-components/ChangeUserRoleDialog.tsx
@@ -3,10 +3,11 @@ import { getUserRoleLabel } from "@/shared/lib/api/userRole";
import { t } from "@lingui/core/macro";
import { Trans } from "@lingui/react/macro";
import { AlertDialog } from "@repo/ui/components/AlertDialog";
+import { Button } from "@repo/ui/components/Button";
import { Modal } from "@repo/ui/components/Modal";
import { Select, SelectItem } from "@repo/ui/components/Select";
import { toastQueue } from "@repo/ui/components/Toast";
-import { useCallback } from "react";
+import { useCallback, useState } from "react";
type UserDetails = components["schemas"]["UserDetails"];
@@ -17,29 +18,38 @@ interface ChangeUserRoleDialogProps {
}
export function ChangeUserRoleDialog({ user, isOpen, onOpenChange }: Readonly
) {
+ const [selectedRole, setSelectedRole] = useState(null);
const changeUserRoleMutation = api.useMutation("put", "/api/account-management/users/{id}/change-user-role");
- const handleUserRoleChange = useCallback(
- async (newUserRole: UserRole) => {
- if (!user) {
- return null;
- }
+ const handleConfirm = useCallback(async () => {
+ if (!user || !selectedRole) {
+ return;
+ }
- changeUserRoleMutation
- .mutateAsync({ params: { path: { id: user.id } }, body: { userRole: newUserRole } })
- .then(() => {
- const userDisplayName = `${user.firstName ?? ""} ${user.lastName ?? ""}`.trim() || user.email;
- toastQueue.add({
- title: t`Success`,
- description: t`User role updated successfully for ${userDisplayName}`,
- variant: "success"
- });
+ try {
+ await changeUserRoleMutation.mutateAsync({
+ params: { path: { id: user.id } },
+ body: { userRole: selectedRole }
+ });
- onOpenChange(false);
- });
- },
- [user, changeUserRoleMutation, onOpenChange]
- );
+ const userDisplayName = `${user.firstName ?? ""} ${user.lastName ?? ""}`.trim() || user.email;
+ toastQueue.add({
+ title: t`Success`,
+ description: t`User role updated successfully for ${userDisplayName}`,
+ variant: "success"
+ });
+
+ onOpenChange(false);
+ setSelectedRole(null);
+ } catch (_error) {
+ // Error is handled by the mutation
+ }
+ }, [user, selectedRole, changeUserRoleMutation, onOpenChange]);
+
+ const handleCancel = useCallback(() => {
+ onOpenChange(false);
+ setSelectedRole(null);
+ }, [onOpenChange]);
return (
@@ -55,8 +65,8 @@ export function ChangeUserRoleDialog({ user, isOpen, onOpenChange }: Readonly handleUserRoleChange(key as UserRole)}
+ selectedKey={selectedRole || user?.role}
+ onSelectionChange={(key) => setSelectedRole(key as UserRole)}
className="flex w-full flex-col"
>
{Object.values(UserRole).map((userRole) => (
@@ -65,6 +75,15 @@ export function ChangeUserRoleDialog({ user, isOpen, onOpenChange }: Readonly
))}
+
+
+
+
+
diff --git a/application/account-management/WebApp/routes/admin/users/-components/InviteUserDialog.tsx b/application/account-management/WebApp/routes/admin/users/-components/InviteUserDialog.tsx
index fe1089e12..65674d353 100644
--- a/application/account-management/WebApp/routes/admin/users/-components/InviteUserDialog.tsx
+++ b/application/account-management/WebApp/routes/admin/users/-components/InviteUserDialog.tsx
@@ -39,7 +39,7 @@ export default function InviteUserDialog({ isOpen, onOpenChange }: ReadonlyInvite user
- An invitation email will be sent to the user with a link to log in.
+ An email with login instructions will be sent to the user.