Add user profile fields and completion prompt
Build and Push Docker Image / build-and-push (push) Successful in 23s

Added `FirstName`, `LastName`, and `Address` fields to `ApplicationUser` for extended user profiles. Updated `Index.razor` to include these fields in the profile form with validation. Introduced `ProfileCompletionService` to check profile completeness and added a prompt in `TopBar.razor` for incomplete profiles.

Created migration `20260518213355_AddUserProfileFields` to update the database schema. Updated `RegistryService` to use new fields for user display names. Registered `ProfileCompletionService` and `DbContextFactory` in `Program.cs`. Styled the profile completion banner in `TopBar.razor.css`.
This commit is contained in:
Arne Moerman
2026-05-18 23:56:28 +02:00
parent c36e04029b
commit fa704ab996
10 changed files with 541 additions and 18 deletions
@@ -95,18 +95,22 @@ internal sealed class RegistryService(RegistryDbContext registryDbContext, Appli
.ToListAsync(cancellationToken)
.ConfigureAwait(false);
var usersById = await applicationDbContext.Users
var users = await applicationDbContext.Users
.Where(x => admins.Contains(x.Id))
.Select(x => new { x.Id, x.Email })
.ToDictionaryAsync(x => x.Id, x => x.Email, cancellationToken)
.Select(x => new { x.Id, x.Email, x.FirstName, x.LastName })
.ToListAsync(cancellationToken)
.ConfigureAwait(false);
var usersById = users.ToDictionary(
x => x.Id,
x => BuildUserDisplayName(x.FirstName, x.LastName, x.Email, x.Id));
return admins
.Select(userId => new RegistryAdminDisplayModel
{
UserId = userId,
DisplayName = usersById.TryGetValue(userId, out var email) && !string.IsNullOrWhiteSpace(email)
? email
DisplayName = usersById.TryGetValue(userId, out var displayName)
? displayName
: userId
})
.ToList();
@@ -180,12 +184,16 @@ internal sealed class RegistryService(RegistryDbContext registryDbContext, Appli
userIds.Add(contribution.UserId);
}
var usersById = await applicationDbContext.Users
var users = await applicationDbContext.Users
.Where(x => userIds.Contains(x.Id))
.Select(x => new { x.Id, x.Email })
.ToDictionaryAsync(x => x.Id, x => x.Email ?? x.Id, cancellationToken)
.Select(x => new { x.Id, x.Email, x.FirstName, x.LastName })
.ToListAsync(cancellationToken)
.ConfigureAwait(false);
var usersById = users.ToDictionary(
x => x.Id,
x => BuildUserDisplayName(x.FirstName, x.LastName, x.Email, x.Id));
var purchasesByItemId = purchases
.GroupBy(x => x.RegistryItemId)
.ToDictionary(g => g.Key, g => g.ToList());
@@ -422,12 +430,16 @@ internal sealed class RegistryService(RegistryDbContext registryDbContext, Appli
userIds.Add(contribution.UserId);
}
var usersById = await applicationDbContext.Users
var users = await applicationDbContext.Users
.Where(x => userIds.Contains(x.Id))
.Select(x => new { x.Id, x.Email })
.ToDictionaryAsync(x => x.Id, x => x.Email ?? x.Id, cancellationToken)
.Select(x => new { x.Id, x.Email, x.FirstName, x.LastName })
.ToListAsync(cancellationToken)
.ConfigureAwait(false);
var usersById = users.ToDictionary(
x => x.Id,
x => BuildUserDisplayName(x.FirstName, x.LastName, x.Email, x.Id));
var purchasesByItemId = purchases
.GroupBy(x => x.RegistryItemId)
.ToDictionary(g => g.Key, g => g.ToList());
@@ -508,12 +520,16 @@ internal sealed class RegistryService(RegistryDbContext registryDbContext, Appli
userIds.Add(contribution.UserId);
}
var usersById = await applicationDbContext.Users
var users = await applicationDbContext.Users
.Where(x => userIds.Contains(x.Id))
.Select(x => new { x.Id, x.Email })
.ToDictionaryAsync(x => x.Id, x => x.Email ?? x.Id, cancellationToken)
.Select(x => new { x.Id, x.Email, x.FirstName, x.LastName })
.ToListAsync(cancellationToken)
.ConfigureAwait(false);
var usersById = users.ToDictionary(
x => x.Id,
x => BuildUserDisplayName(x.FirstName, x.LastName, x.Email, x.Id));
return new RegistryItemEditModel
{
Id = item.Id,
@@ -907,4 +923,20 @@ internal sealed class RegistryService(RegistryDbContext registryDbContext, Appli
var fallback = Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString("N")))).Substring(0, 16).ToLowerInvariant();
return fallback;
}
private static string BuildUserDisplayName(string? firstName, string? lastName, string? email, string userId)
{
var fullName = $"{firstName} {lastName}".Trim();
if (!string.IsNullOrWhiteSpace(fullName))
{
return fullName;
}
if (!string.IsNullOrWhiteSpace(email))
{
return email;
}
return userId;
}
}