Files
BirthList/src/BirthList.Web/Components/Pages/RegistryAdmin.razor.cs
T
Arne Moerman b1b7525759
Build and Push Docker Image / build-and-push (push) Successful in 1m21s
Update culture defaults and improve second-hand dropdown
- Change default request culture to "nl-BE" and reorder supported cultures; remove "nl-NL"
- Refactor RegistryAdmin second-hand preference to use a string property for better nullable handling in the dropdown
2026-07-01 15:27:49 +02:00

809 lines
29 KiB
C#

using BirthList.Domain.Entities;
using BirthList.Web.Authorization;
using BirthList.Web.Features.Registries;
using BirthList.Web.Services;
using Blazored.TextEditor;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.JSInterop;
namespace BirthList.Web.Components.Pages;
[Authorize]
public partial class RegistryAdmin : ComponentBase
{
[Parameter] public Guid RegistryId { get; set; }
[Inject] private RegistryService RegistryService { get; set; } = null!;
[Inject] private RegistryMetadataService RegistryMetadataService { get; set; } = null!;
[Inject] private RegistryAuthorizationService RegistryAuthorizationService { get; set; } = null!;
[Inject] private RegistryUserContext RegistryUserContext { get; set; } = null!;
[Inject] private NavigationManager NavigationManager { get; set; } = null!;
[Inject] private SmtpEmailSender EmailSender { get; set; } = null!;
[Inject] private SmtpConfigurationStatusService SmtpConfigurationStatusService { get; set; } = null!;
[Inject] private IJSRuntime JSRuntime { get; set; } = null!;
protected RegistrySettingsEditModel SettingsModel { get; } = new();
protected RegistryItemEditModel ItemModel { get; private set; } = new();
protected string PreferSecondHandString
{
get => ItemModel.PreferSecondHand switch
{
null => "",
true => "true",
false => "false"
};
set => ItemModel.PreferSecondHand = value switch
{
"true" => true,
"false" => false,
_ => null
};
}
protected IReadOnlyList<RegistryItemEditModel> Items { get; private set; } = [];
protected IReadOnlyList<RegistryItemCategoryEditModel> ItemCategories { get; private set; } = [];
protected string? NewCategoryName { get; set; }
protected string? CategoryRenameName { get; set; }
protected Guid? EditingCategoryId { get; set; }
protected string? ItemManagementMessage { get; private set; }
protected IReadOnlyList<RegistryAdminDisplayModel> Admins { get; private set; } = [];
protected IReadOnlyList<RegistryAccessibleUserAddressViewModel> AccessibleUserAddresses { get; private set; } = [];
protected bool IsAuthorized { get; private set; }
protected bool IsSmtpConfigured { get; private set; }
protected string? InviteEmail { get; set; }
protected string? InviteLink { get; private set; }
protected BlazoredTextEditor? TextEditor { get; set; }
protected string ActiveTab { get; set; } = "items";
protected RenderFragment ToolbarContent => builder =>
{
builder.AddMarkupContent(0, @"<span class='ql-formats'><select class='ql-size'><option value='small'></option><option selected></option><option value='large'></option><option value='huge'></option></select><select class='ql-header'><option selected></option><option value='1'></option><option value='2'></option><option value='3'></option><option value='4'></option><option value='5'></option></select></span>");
builder.AddMarkupContent(1, @"<span class='ql-formats'><button class='ql-bold'></button><button class='ql-italic'></button><button class='ql-underline'></button><button class='ql-strike'></button></span>");
builder.AddMarkupContent(2, @"<span class='ql-formats'><select class=""ql-color""></select><select class=""ql-background""></select></span>");
builder.AddMarkupContent(3, @"<span class='ql-formats'><button class='ql-list' value='ordered'></button><button class='ql-list' value='bullet'></button></span>");
builder.AddMarkupContent(4, @"<span class='ql-formats'><button class='ql-link'></button><button class='ql-clean'></button></span>");
};
private bool _pendingEditorLoad;
private string? _lastLoadedHeaderContentHtml;
private Guid? _draggedItemId;
private Guid? _draggedCategoryId;
private Guid? _itemDropCategoryId;
private Guid? _itemDropTargetItemId;
private bool _itemDropAfterTarget;
private Guid? _categoryDropTargetId;
private bool _categoryDropAfterTarget;
private bool _categoryDropAtEnd;
private bool _dropOperationInProgress;
protected override async Task OnParametersSetAsync()
{
var userId = await RegistryUserContext.GetUserIdAsync(CancellationToken.None).ConfigureAwait(false);
if (string.IsNullOrWhiteSpace(userId))
{
IsAuthorized = false;
return;
}
IsAuthorized = await RegistryAuthorizationService.IsRegistryAdminAsync(RegistryId, userId, CancellationToken.None).ConfigureAwait(false);
if (!IsAuthorized)
{
return;
}
IsSmtpConfigured = SmtpConfigurationStatusService.IsConfigured();
await LoadAsync().ConfigureAwait(false);
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (TextEditor is null || !_pendingEditorLoad)
{
return;
}
try
{
await TextEditor.LoadHTMLContent(SettingsModel.HeaderContentHtml ?? string.Empty).ConfigureAwait(false);
_lastLoadedHeaderContentHtml = SettingsModel.HeaderContentHtml ?? string.Empty;
_pendingEditorLoad = false;
}
catch (JSException)
{
// Editor scripts may still be initializing on first render in some environments.
}
catch (InvalidOperationException)
{
// Ignore transient prerender/circuit timing issues.
}
}
protected void SetActiveTab(string tab)
{
ActiveTab = tab;
}
protected string GetTabClass(string tab)
{
return ActiveTab == tab ? "active" : "";
}
protected string GetTabPaneClass(string tab)
{
return ActiveTab == tab ? "show active" : "";
}
protected async Task SaveSettingsAsync()
{
if (TextEditor is not null)
{
try
{
SettingsModel.HeaderContentHtml = await TextEditor.GetHTML().ConfigureAwait(false);
}
catch (JSException)
{
// Preserve existing content when editor JS isn't ready yet.
}
catch (InvalidOperationException)
{
// Preserve existing content when interop isn't available.
}
}
await RegistryService.UpdateRegistrySettingsAsync(RegistryId, SettingsModel, CancellationToken.None).ConfigureAwait(false);
}
protected async Task SaveItemAsync()
{
ItemManagementMessage = null;
if (!ItemModel.CategoryId.HasValue && ItemCategories.Count > 0)
{
ItemModel.CategoryId = ItemCategories[0].Id;
}
await RegistryService.UpsertRegistryItemAsync(RegistryId, ItemModel, CancellationToken.None).ConfigureAwait(false);
await ReloadItemManagementAsync().ConfigureAwait(false);
ItemModel = new RegistryItemEditModel
{
CurrencyCode = SettingsModel.CurrencyCode,
DesiredQuantity = 1,
CategoryId = ItemCategories.FirstOrDefault()?.Id
};
}
protected async Task DeleteItemAsync(Guid? itemId)
{
if (!itemId.HasValue)
{
return;
}
ItemManagementMessage = null;
await RegistryService.DeleteRegistryItemAsync(RegistryId, itemId.Value, CancellationToken.None).ConfigureAwait(false);
await ReloadItemManagementAsync().ConfigureAwait(false);
}
protected void EditItem(RegistryItemEditModel model)
{
ItemManagementMessage = null;
ItemModel = new RegistryItemEditModel
{
Id = model.Id,
CategoryId = model.CategoryId,
Name = model.Name,
PictureUrl = model.PictureUrl,
ProductUrl = model.ProductUrl,
Description = model.Description,
PriceAmount = model.PriceAmount,
CurrencyCode = model.CurrencyCode,
DesiredQuantity = model.DesiredQuantity,
ParticipationAllowed = model.ParticipationAllowed,
ParticipationTargetAmount = model.ParticipationTargetAmount,
PreferSecondHand = model.PreferSecondHand,
IsGiven = model.IsGiven
};
}
protected async Task AddCategoryAsync()
{
ItemManagementMessage = null;
if (string.IsNullOrWhiteSpace(NewCategoryName))
{
ItemManagementMessage = "Category name is required.";
return;
}
try
{
await RegistryService.AddRegistryItemCategoryAsync(RegistryId, NewCategoryName, CancellationToken.None).ConfigureAwait(false);
NewCategoryName = null;
await ReloadItemManagementAsync().ConfigureAwait(false);
ItemModel.CategoryId ??= ItemCategories.LastOrDefault()?.Id;
}
catch (InvalidOperationException ex)
{
ItemManagementMessage = ex.Message;
}
}
protected void StartCategoryRename(RegistryItemCategoryEditModel category)
{
ArgumentNullException.ThrowIfNull(category);
ItemManagementMessage = null;
EditingCategoryId = category.Id;
CategoryRenameName = category.Name;
}
protected void CancelCategoryRename()
{
EditingCategoryId = null;
CategoryRenameName = null;
}
protected async Task SaveCategoryRenameAsync(Guid categoryId)
{
ItemManagementMessage = null;
if (string.IsNullOrWhiteSpace(CategoryRenameName))
{
ItemManagementMessage = "Category name is required.";
return;
}
try
{
await RegistryService.RenameRegistryItemCategoryAsync(RegistryId, categoryId, CategoryRenameName, CancellationToken.None).ConfigureAwait(false);
await ReloadItemManagementAsync().ConfigureAwait(false);
EditingCategoryId = null;
CategoryRenameName = null;
}
catch (InvalidOperationException ex)
{
ItemManagementMessage = ex.Message;
}
}
protected async Task RemoveCategoryAsync(Guid categoryId)
{
ItemManagementMessage = null;
if (ItemCategories.Count <= 1)
{
ItemManagementMessage = "At least one category is required.";
return;
}
try
{
await RegistryService.RemoveRegistryItemCategoryAsync(RegistryId, categoryId, CancellationToken.None).ConfigureAwait(false);
await ReloadItemManagementAsync().ConfigureAwait(false);
if (ItemModel.CategoryId == categoryId)
{
ItemModel.CategoryId = ItemCategories.FirstOrDefault()?.Id;
}
}
catch (InvalidOperationException ex)
{
ItemManagementMessage = ex.Message;
}
}
protected void OnItemDragStart(Guid itemId)
{
_draggedItemId = itemId;
_draggedCategoryId = null;
_itemDropCategoryId = null;
_itemDropTargetItemId = null;
_itemDropAfterTarget = false;
_categoryDropTargetId = null;
_categoryDropAfterTarget = false;
_categoryDropAtEnd = false;
}
protected void OnItemDragEnd()
{
_draggedItemId = null;
_itemDropCategoryId = null;
_itemDropTargetItemId = null;
_itemDropAfterTarget = false;
}
protected void OnCategoryDragStart(Guid categoryId)
{
_draggedCategoryId = categoryId;
_draggedItemId = null;
_itemDropCategoryId = null;
_itemDropTargetItemId = null;
_itemDropAfterTarget = false;
_categoryDropTargetId = null;
_categoryDropAfterTarget = false;
_categoryDropAtEnd = false;
}
protected void OnCategoryDragEnd()
{
_draggedCategoryId = null;
_categoryDropTargetId = null;
_categoryDropAfterTarget = false;
_categoryDropAtEnd = false;
}
protected static void OnDragOver(DragEventArgs _)
{
}
protected async Task OnItemDragOverAsync(Guid categoryId, Guid targetItemId, DragEventArgs args)
{
if (!_draggedItemId.HasValue)
{
return;
}
_itemDropCategoryId = categoryId;
_itemDropTargetItemId = targetItemId;
var isBottomHalf = await IsPointerInBottomHalfAsync($"item-row-{targetItemId}", args.ClientY).ConfigureAwait(false);
_itemDropAfterTarget = isBottomHalf;
await InvokeAsync(StateHasChanged);
}
protected void OnCategoryItemsDragOver(Guid categoryId)
{
if (!_draggedItemId.HasValue)
{
return;
}
var category = ItemCategories.FirstOrDefault(x => x.Id == categoryId);
if (category is null || category.Items.Count > 0)
{
return;
}
_itemDropCategoryId = categoryId;
_itemDropTargetItemId = null;
_itemDropAfterTarget = false;
}
protected async Task OnCategoryDragOverAsync(Guid categoryId, DragEventArgs args)
{
if (!_draggedCategoryId.HasValue)
{
return;
}
_categoryDropTargetId = categoryId;
_categoryDropAfterTarget = await IsPointerInBottomHalfAsync($"category-card-{categoryId}", args.ClientY).ConfigureAwait(false);
_categoryDropAtEnd = false;
await InvokeAsync(StateHasChanged);
}
protected void OnCategoryListEndDragOver()
{
if (!_draggedCategoryId.HasValue)
{
return;
}
_categoryDropTargetId = null;
_categoryDropAfterTarget = false;
_categoryDropAtEnd = true;
}
protected string GetItemDropClass(Guid categoryId, Guid itemId)
{
if (_itemDropCategoryId != categoryId || _itemDropTargetItemId != itemId)
{
return string.Empty;
}
return _itemDropAfterTarget ? "drop-target-item-after" : "drop-target-item-before";
}
protected string GetCategoryItemsDropClass(Guid categoryId)
{
return _itemDropCategoryId == categoryId && !_itemDropTargetItemId.HasValue
? "drop-target-item-end"
: string.Empty;
}
protected string GetCategoryDropClass(Guid categoryId)
{
if (_categoryDropTargetId != categoryId)
{
return string.Empty;
}
return _categoryDropAfterTarget ? "drop-target-category-after" : "drop-target-category-before";
}
protected string GetCategoryListEndDropClass()
{
return _categoryDropAtEnd ? "drop-target-category-list-end" : string.Empty;
}
protected async Task OnItemDropAsync(Guid categoryId, Guid targetItemId)
{
if (!_draggedItemId.HasValue || _dropOperationInProgress)
{
return;
}
_dropOperationInProgress = true;
try
{
var draggedItem = Items.FirstOrDefault(x => x.Id == _draggedItemId.Value);
var targetCategory = ItemCategories.FirstOrDefault(x => x.Id == categoryId);
if (draggedItem is null || targetCategory is null)
{
_draggedItemId = null;
_itemDropCategoryId = null;
_itemDropTargetItemId = null;
_itemDropAfterTarget = false;
return;
}
var targetItems = targetCategory.Items.Where(x => x.Id.HasValue).ToList();
var targetIndex = targetItems.FindIndex(x => x.Id == targetItemId);
if (targetIndex < 0)
{
targetIndex = targetItems.Count;
}
var placeAfter = _itemDropTargetItemId == targetItemId && _itemDropAfterTarget;
if (placeAfter)
{
targetIndex++;
}
if (draggedItem.CategoryId == categoryId)
{
var currentIndex = targetItems.FindIndex(x => x.Id == _draggedItemId.Value);
if (currentIndex >= 0 && currentIndex < targetIndex)
{
targetIndex--;
}
}
await RegistryService.MoveRegistryItemAsync(RegistryId, _draggedItemId.Value, categoryId, targetIndex, CancellationToken.None).ConfigureAwait(false);
await ReloadItemManagementAsync().ConfigureAwait(false);
_draggedItemId = null;
_itemDropCategoryId = null;
_itemDropTargetItemId = null;
_itemDropAfterTarget = false;
}
finally
{
_dropOperationInProgress = false;
}
}
protected async Task OnCategoryItemsDropAsync(Guid categoryId)
{
if (!_draggedItemId.HasValue || _dropOperationInProgress)
{
return;
}
_dropOperationInProgress = true;
try
{
var targetCategory = ItemCategories.FirstOrDefault(x => x.Id == categoryId);
if (targetCategory is null)
{
_draggedItemId = null;
_itemDropCategoryId = null;
_itemDropTargetItemId = null;
_itemDropAfterTarget = false;
return;
}
var targetIndex = targetCategory.Items.Count;
await RegistryService.MoveRegistryItemAsync(RegistryId, _draggedItemId.Value, categoryId, targetIndex, CancellationToken.None).ConfigureAwait(false);
await ReloadItemManagementAsync().ConfigureAwait(false);
_draggedItemId = null;
_itemDropCategoryId = null;
_itemDropTargetItemId = null;
_itemDropAfterTarget = false;
}
finally
{
_dropOperationInProgress = false;
}
}
protected async Task OnCategoryDropAsync(Guid targetCategoryId)
{
if (!_draggedCategoryId.HasValue || _draggedCategoryId == targetCategoryId || _dropOperationInProgress)
{
return;
}
_dropOperationInProgress = true;
try
{
var targetIndex = ItemCategories.ToList().FindIndex(x => x.Id == targetCategoryId);
if (targetIndex < 0)
{
_draggedCategoryId = null;
_categoryDropTargetId = null;
_categoryDropAfterTarget = false;
return;
}
var placeAfter = _categoryDropTargetId == targetCategoryId && _categoryDropAfterTarget;
if (placeAfter)
{
targetIndex++;
}
var sourceIndex = ItemCategories.ToList().FindIndex(x => x.Id == _draggedCategoryId.Value);
if (sourceIndex >= 0 && sourceIndex < targetIndex)
{
targetIndex--;
}
await RegistryService.MoveRegistryCategoryAsync(RegistryId, _draggedCategoryId.Value, targetIndex, CancellationToken.None).ConfigureAwait(false);
await ReloadItemManagementAsync().ConfigureAwait(false);
_draggedCategoryId = null;
_categoryDropTargetId = null;
_categoryDropAfterTarget = false;
_categoryDropAtEnd = false;
}
finally
{
_dropOperationInProgress = false;
}
}
protected async Task OnCategoryListEndDropAsync()
{
if (!_draggedCategoryId.HasValue || _dropOperationInProgress)
{
return;
}
_dropOperationInProgress = true;
try
{
await RegistryService.MoveRegistryCategoryAsync(RegistryId, _draggedCategoryId.Value, ItemCategories.Count, CancellationToken.None).ConfigureAwait(false);
await ReloadItemManagementAsync().ConfigureAwait(false);
_draggedCategoryId = null;
_categoryDropTargetId = null;
_categoryDropAfterTarget = false;
_categoryDropAtEnd = false;
}
finally
{
_dropOperationInProgress = false;
}
}
protected async Task FetchMetadataAsync()
{
if (string.IsNullOrWhiteSpace(ItemModel.ProductUrl))
{
return;
}
var userId = await RegistryUserContext.GetUserIdAsync(CancellationToken.None).ConfigureAwait(false);
var actorUserId = string.IsNullOrWhiteSpace(userId) ? "unknown-user" : userId;
async Task LogMetadataAsync(UserActionType actionType, string details)
{
try
{
await RegistryService.LogUserActionAsync(
RegistryId,
actorUserId,
actionType,
ItemModel.Id,
TruncateLogDetails(details),
CancellationToken.None).ConfigureAwait(false);
}
catch (ArgumentException)
{
// Keep autofetch UX stable even if logging cannot be persisted.
}
}
try
{
var metadata = await RegistryMetadataService.FetchAsync(ItemModel.ProductUrl, CancellationToken.None).ConfigureAwait(false);
if (metadata is null)
{
await LogMetadataAsync(UserActionType.MetadataFetchFailed, $"metadata-fetch failed; url={ItemModel.ProductUrl}; reason=no-metadata").ConfigureAwait(false);
return;
}
if (!string.IsNullOrWhiteSpace(metadata.NormalizedUrl))
{
ItemModel.ProductUrl = metadata.NormalizedUrl;
}
if ((string.IsNullOrWhiteSpace(ItemModel.Name) || string.Equals(ItemModel.Name, "Amazon", StringComparison.OrdinalIgnoreCase)) && !string.IsNullOrWhiteSpace(metadata.Title))
{
ItemModel.Name = metadata.Title;
}
if ((string.IsNullOrWhiteSpace(ItemModel.Description) || string.Equals(ItemModel.Description, "Amazon", StringComparison.OrdinalIgnoreCase)) && !string.IsNullOrWhiteSpace(metadata.Description))
{
ItemModel.Description = metadata.Description;
}
if (string.IsNullOrWhiteSpace(ItemModel.PictureUrl) && !string.IsNullOrWhiteSpace(metadata.ImageUrl))
{
ItemModel.PictureUrl = metadata.ImageUrl;
}
if (!ItemModel.PriceAmount.HasValue && metadata.PriceAmount.HasValue)
{
ItemModel.PriceAmount = metadata.PriceAmount;
}
if (!string.IsNullOrWhiteSpace(metadata.CurrencyCode))
{
ItemModel.CurrencyCode = metadata.CurrencyCode;
}
var summary = $"metadata-fetch success; url={ItemModel.ProductUrl}; title={(string.IsNullOrWhiteSpace(metadata.Title) ? "-" : metadata.Title)}; image={(string.IsNullOrWhiteSpace(metadata.ImageUrl) ? "no" : "yes")}; price={(metadata.PriceAmount.HasValue ? metadata.PriceAmount.Value.ToString("0.00") : "-")}; currency={(string.IsNullOrWhiteSpace(metadata.CurrencyCode) ? "-" : metadata.CurrencyCode)}";
await LogMetadataAsync(UserActionType.MetadataFetchSucceeded, summary).ConfigureAwait(false);
}
catch (HttpRequestException ex)
{
await LogMetadataAsync(UserActionType.MetadataFetchFailed, $"metadata-fetch failed; url={ItemModel.ProductUrl}; reason=http; message={ex.Message}").ConfigureAwait(false);
}
catch (TaskCanceledException ex)
{
await LogMetadataAsync(UserActionType.MetadataFetchFailed, $"metadata-fetch failed; url={ItemModel.ProductUrl}; reason=timeout-or-cancel; message={ex.Message}").ConfigureAwait(false);
}
catch (InvalidOperationException ex)
{
await LogMetadataAsync(UserActionType.MetadataFetchFailed, $"metadata-fetch failed; url={ItemModel.ProductUrl}; reason=invalid-operation; message={ex.Message}").ConfigureAwait(false);
}
catch (Exception ex)
{
await LogMetadataAsync(UserActionType.MetadataFetchFailed, $"metadata-fetch failed; url={ItemModel.ProductUrl}; reason=unexpected-{ex.GetType().Name}; message={ex.Message}").ConfigureAwait(false);
}
}
private static string TruncateLogDetails(string details)
{
if (string.IsNullOrWhiteSpace(details))
{
return string.Empty;
}
return details.Length <= 500 ? details : details[..500];
}
protected async Task CreateInviteAsync()
{
var inviteId = await RegistryService.CreateAdminInviteAsync(RegistryId, InviteEmail, TimeSpan.FromDays(7), CancellationToken.None).ConfigureAwait(false);
var token = await RegistryService.GetInviteTokenAsync(inviteId, CancellationToken.None).ConfigureAwait(false);
if (string.IsNullOrWhiteSpace(token))
{
return;
}
InviteLink = NavigationManager.ToAbsoluteUri($"/registry/{RegistryId}/invite/{Uri.EscapeDataString(token)}").AbsoluteUri;
if (!string.IsNullOrWhiteSpace(InviteEmail))
{
await EmailSender.SendInviteAsync(InviteEmail, InviteLink).ConfigureAwait(false);
}
Admins = await RegistryService.GetRegistryAdminsAsync(RegistryId, CancellationToken.None).ConfigureAwait(false);
}
protected async Task DeleteAdminAsync(string? userId)
{
if (string.IsNullOrWhiteSpace(userId))
{
return;
}
await RegistryService.RemoveRegistryAdminAsync(RegistryId, userId, CancellationToken.None).ConfigureAwait(false);
Admins = await RegistryService.GetRegistryAdminsAsync(RegistryId, CancellationToken.None).ConfigureAwait(false);
}
protected void AddContributionAmountQrCode()
{
SettingsModel.ContributionAmountQrCodes.Add(new ContributionAmountQrCodeModel());
}
protected void RemoveContributionAmountQrCode(ContributionAmountQrCodeModel model)
{
ArgumentNullException.ThrowIfNull(model);
SettingsModel.ContributionAmountQrCodes.Remove(model);
}
private async Task LoadAsync()
{
var settings = await RegistryService.GetRegistrySettingsAsync(RegistryId, CancellationToken.None).ConfigureAwait(false);
if (settings is not null)
{
SettingsModel.BabyName = settings.BabyName;
SettingsModel.BirthDate = settings.BirthDate;
SettingsModel.HeaderContentHtml = settings.HeaderContentHtml;
SettingsModel.ShippingAddress = settings.ShippingAddress;
SettingsModel.CurrencyCode = settings.CurrencyCode;
SettingsModel.ThemeKey = settings.ThemeKey;
SettingsModel.BankAccountDisplayName = settings.BankAccountDisplayName;
SettingsModel.BankAccountIban = settings.BankAccountIban;
SettingsModel.BankAccountBic = settings.BankAccountBic;
SettingsModel.ShowBankAccountName = settings.ShowBankAccountName;
SettingsModel.ContributionQrCodeUrl = settings.ContributionQrCodeUrl;
SettingsModel.ContributionAmountQrCodes = settings.ContributionAmountQrCodes;
var currentHeaderContent = SettingsModel.HeaderContentHtml ?? string.Empty;
_pendingEditorLoad = currentHeaderContent != _lastLoadedHeaderContentHtml;
}
Admins = await RegistryService.GetRegistryAdminsAsync(RegistryId, CancellationToken.None).ConfigureAwait(false);
AccessibleUserAddresses = await RegistryService.GetRegistryAccessibleUserAddressesAsync(RegistryId, CancellationToken.None).ConfigureAwait(false);
await ReloadItemManagementAsync().ConfigureAwait(false);
ItemModel = new RegistryItemEditModel
{
CurrencyCode = SettingsModel.CurrencyCode,
DesiredQuantity = 1,
CategoryId = ItemCategories.FirstOrDefault()?.Id
};
}
private async Task ReloadItemManagementAsync()
{
var categories = await RegistryService.GetRegistryItemCategoriesAsync(RegistryId, CancellationToken.None).ConfigureAwait(false);
var items = await RegistryService.GetRegistryItemsAsync(RegistryId, CancellationToken.None).ConfigureAwait(false);
Items = items;
var itemsByCategory = items
.Where(x => x.CategoryId.HasValue)
.GroupBy(x => x.CategoryId!.Value)
.ToDictionary(g => g.Key, g => (IReadOnlyList<RegistryItemEditModel>)g.ToList());
ItemCategories = categories
.OrderBy(x => x.SortOrder)
.Select(category => new RegistryItemCategoryEditModel
{
Id = category.Id,
Name = category.Name,
SortOrder = category.SortOrder,
Items = itemsByCategory.TryGetValue(category.Id, out var categoryItems)
? categoryItems
: []
})
.ToList();
}
private async Task<bool> IsPointerInBottomHalfAsync(string elementId, double clientY)
{
try
{
return await JSRuntime.InvokeAsync<bool>("birthListDrag.isPointerInBottomHalf", elementId, clientY).ConfigureAwait(false);
}
catch (JSException)
{
return false;
}
catch (InvalidOperationException)
{
return false;
}
}
}