Add UserActionLog and enhance registry features
Build and Push Docker Image / build-and-push (push) Successful in 1m59s

- Introduced `UserActionLog` entity to track user actions.
- Replaced `CanBeSecondHand` with `PreferSecondHand` property.
- Added `ShowBankAccountName` to `RegistrySettings`.
- Updated models and migrations for new properties.
- Enhanced `RegistryService` with user action logging and item details.
- Redesigned `Home.razor` with a grid layout and modal for registries.
- Added `RegistryActionLog.razor` for admin action logs.
- Improved `RegistryPublic.razor` with purchaser/contributor details.
- Replaced sidebar with `TopBar.razor` for responsive navigation.
- Updated CSS for new components and improved responsiveness.
This commit is contained in:
Arne Moerman
2026-05-17 20:57:54 +02:00
parent 6b593828d7
commit 2bf0295508
32 changed files with 3811 additions and 435 deletions
+156 -70
View File
@@ -1,86 +1,172 @@
@page "/"
@using BirthList.Web.Features.Registries
<PageTitle>Birth Registry</PageTitle>
<h1>Birth Registry</h1>
<h1>Welcome to Gift List</h1>
<AuthorizeView>
<Authorized Context="authState">
<div class="mb-4">
<h2>Create your registry</h2>
<EditForm Model="Model" OnValidSubmit="CreateRegistryAsync" Context="formContext" FormName="create-registry-form">
<DataAnnotationsValidator />
<div class="row g-3">
<div class="col-md-5">
<label class="form-label">Title</label>
<InputText class="form-control" @bind-Value="Model.Title" />
<div class="registry-sections">
<div class="mb-4">
<div class="section-header">
<h2>Registries you manage</h2>
<button class="btn btn-primary btn-sm" @onclick="() => ShowCreateForm = !ShowCreateForm">
<span class="bi bi-plus"></span> Create new
</button>
</div>
@if (MyRegistries.Count == 0)
{
<p class="text-muted">No registries yet.</p>
}
else
{
<div class="registry-grid">
@foreach (var registry in MyRegistries)
{
<div class="registry-card">
<h3>@registry.Title</h3>
<div class="registry-actions">
<a href="/registry/@registry.PublicLinkCode" class="btn btn-outline-primary btn-sm">View</a>
<a href="/registry/@registry.Id/admin" class="btn btn-outline-secondary btn-sm">Manage</a>
</div>
</div>
}
</div>
<div class="col-md-3">
<label class="form-label">Type</label>
<InputSelect class="form-select" @bind-Value="Model.RegistryType">
<option value="@BirthList.Domain.Entities.RegistryType.Birth">Birth</option>
<option value="@BirthList.Domain.Entities.RegistryType.Wedding">Wedding</option>
<option value="@BirthList.Domain.Entities.RegistryType.Birthday">Birthday</option>
</InputSelect>
}
</div>
<div class="mb-4">
<h2>Visited registries</h2>
@if (VisitedRegistries.Count == 0)
{
<p class="text-muted">No visited registries yet.</p>
}
else
{
<div class="registry-grid">
@foreach (var registry in VisitedRegistries)
{
<div class="registry-card">
<h3>@registry.Title</h3>
<div class="registry-actions">
<a href="/registry/@registry.PublicLinkCode" class="btn btn-outline-primary btn-sm">View</a>
</div>
</div>
}
</div>
<div class="col-md-2">
<label class="form-label">Theme</label>
<InputSelect class="form-select" @bind-Value="Model.ThemeKey">
<option value="default">Default</option>
<option value="soft">Soft</option>
<option value="modern">Modern</option>
</InputSelect>
}
</div>
</div>
@if (ShowCreateForm)
{
<div class="create-registry-modal-overlay" @onclick="() => ShowCreateForm = false">
<div class="create-registry-modal" @onclick:stopPropagation="true">
<div class="modal-header">
<h3 class="modal-title">Create new registry</h3>
<button type="button" class="btn-close" @onclick="() => ShowCreateForm = false"></button>
</div>
<div class="col-md-2 d-flex align-items-end">
<button class="btn btn-primary w-100" type="submit">Create</button>
<div class="modal-body">
<EditForm Model="Model" OnValidSubmit="CreateRegistryAsync" Context="formContext" FormName="create-registry-form">
<DataAnnotationsValidator />
<div class="mb-3">
<label class="form-label">Title</label>
<InputText class="form-control" @bind-Value="Model.Title" />
</div>
<div class="mb-3">
<label class="form-label">Type</label>
<InputSelect class="form-select" @bind-Value="Model.RegistryType">
<option value="@BirthList.Domain.Entities.RegistryType.Birth">Birth</option>
<option value="@BirthList.Domain.Entities.RegistryType.Wedding">Wedding</option>
<option value="@BirthList.Domain.Entities.RegistryType.Birthday">Birthday</option>
</InputSelect>
</div>
<div class="mb-3">
<label class="form-label">Theme</label>
<InputSelect class="form-select" @bind-Value="Model.ThemeKey">
<option value="default">Default</option>
<option value="soft">Soft</option>
<option value="modern">Modern</option>
</InputSelect>
</div>
@if (!string.IsNullOrWhiteSpace(ErrorMessage))
{
<div class="alert alert-danger mb-3">@ErrorMessage</div>
}
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" @onclick="() => ShowCreateForm = false">Cancel</button>
<button class="btn btn-primary" type="submit">Create</button>
</div>
</EditForm>
</div>
</div>
</EditForm>
@if (!string.IsNullOrWhiteSpace(ErrorMessage))
{
<div class="alert alert-danger mt-3">@ErrorMessage</div>
}
</div>
<div class="mb-4">
<h2>Registries you manage</h2>
@if (MyRegistries.Count == 0)
{
<p>No registries yet.</p>
}
else
{
<ul>
@foreach (var registry in MyRegistries)
{
<li>
<a href="/registry/@registry.PublicLinkCode">@registry.Title</a>
&nbsp;|&nbsp;
<a href="/registry/@registry.Id/admin">Admin</a>
</li>
}
</ul>
}
</div>
<div>
<h2>Visited registries</h2>
@if (VisitedRegistries.Count == 0)
{
<p>No visited registries yet.</p>
}
else
{
<ul>
@foreach (var registry in VisitedRegistries)
{
<li><a href="/registry/@registry.PublicLinkCode">@registry.Title</a></li>
}
</ul>
}
</div>
</div>
}
</Authorized>
<NotAuthorized>
<p>Please <a href="Account/Login">log in</a> to create and manage registries.</p>
<div class="alert alert-info mt-4">
<p>Please <a href="Account/Login">log in</a> to create and manage registries.</p>
</div>
</NotAuthorized>
</AuthorizeView>
@code {
[SupplyParameterFromForm(FormName = "create-registry-form")]
protected RegistryCreateModel Model { get; set; } = new();
protected IReadOnlyList<RegistrySummaryViewModel> MyRegistries { get; private set; } = [];
protected IReadOnlyList<RegistrySummaryViewModel> VisitedRegistries { get; private set; } = [];
protected string? ErrorMessage { get; private set; }
protected bool ShowCreateForm { get; private set; }
[Inject] private RegistryService RegistryService { get; set; } = null!;
[Inject] private RegistryUserContext RegistryUserContext { get; set; } = null!;
protected override async Task OnInitializedAsync()
{
await LoadAsync().ConfigureAwait(false);
}
protected async Task CreateRegistryAsync()
{
ErrorMessage = null;
var userId = await RegistryUserContext.GetUserIdAsync(CancellationToken.None).ConfigureAwait(false);
if (string.IsNullOrWhiteSpace(userId))
{
ErrorMessage = "You must be logged in to create a registry.";
return;
}
if (string.IsNullOrWhiteSpace(Model.Title))
{
ErrorMessage = "Title is required.";
return;
}
await RegistryService.CreateRegistryAsync(userId, Model, CancellationToken.None).ConfigureAwait(false);
Model = new RegistryCreateModel
{
RegistryType = Model.RegistryType,
ThemeKey = Model.ThemeKey
};
ShowCreateForm = false;
await LoadAsync().ConfigureAwait(false);
}
private async Task LoadAsync()
{
var userId = await RegistryUserContext.GetUserIdAsync(CancellationToken.None).ConfigureAwait(false);
if (string.IsNullOrWhiteSpace(userId))
{
MyRegistries = [];
VisitedRegistries = [];
return;
}
MyRegistries = await RegistryService.GetAdminRegistriesAsync(userId, CancellationToken.None).ConfigureAwait(false);
VisitedRegistries = await RegistryService.GetVisitedRegistriesAsync(userId, CancellationToken.None).ConfigureAwait(false);
}
}