Refactor currency handling and UI updates

Updated currency handling to use dynamic symbols, defaulting to "€". Adjusted UI components to reflect these changes. Introduced `GetCurrencySymbol` and `NormalizeCurrencySymbol` methods for consistency. Reintroduced QR code parsing/serialization methods. Updated models and services to align with the new currency symbol logic.
This commit is contained in:
Arne Moerman
2026-05-19 22:05:10 +02:00
parent 89c439f80b
commit 9f3ae1051c
6 changed files with 96 additions and 70 deletions
@@ -103,7 +103,7 @@ else
<InputNumber class="form-control" @bind-Value="ItemModel.PriceAmount" />
</div>
<div class="col-md-2">
<label class="form-label">Currency</label>
<label class="form-label">Currency symbol</label>
<InputText class="form-control" @bind-Value="ItemModel.CurrencyCode" />
</div>
<div class="col-md-2">
@@ -304,7 +304,7 @@ else
<InputDate class="form-control" @bind-Value="SettingsModel.BirthDate" />
</div>
<div class="col-md-2">
<label class="form-label">Currency</label>
<label class="form-label">Currency symbol</label>
<InputText class="form-control" @bind-Value="SettingsModel.CurrencyCode" />
</div>
<div class="col-md-3">
@@ -21,11 +21,11 @@ else
<div class="card p-3">
@if (RepresentableAmounts.Count == 0)
{
<p class="text-muted mb-0">No representable amount can be formed from configured QR codes up to 200.</p>
<p class="text-muted mb-0">No representable amount can be formed from configured QR codes up to @GetCurrencySymbol()200.</p>
}
else
{
<label class="form-label">Select amount: @SelectedAmount</label>
<label class="form-label">Select amount: @GetCurrencySymbol()@SelectedAmount</label>
<input type="range"
class="form-range"
min="0"
@@ -45,7 +45,7 @@ else
<ul class="mb-3">
@foreach (var suggestion in Suggestions)
{
<li>@suggestion.RepeatCount x @suggestion.Amount</li>
<li>@suggestion.RepeatCount x @GetCurrencySymbol()@suggestion.Amount</li>
}
</ul>
@@ -55,7 +55,7 @@ else
@for (var i = 0; i < suggestion.RepeatCount; i++)
{
<div>
<div class="small mb-1">@suggestion.Amount</div>
<div class="small mb-1">@GetCurrencySymbol()@suggestion.Amount</div>
<img src="@BuildQrImageUrl(suggestion.QrCodeUrl)" alt="QR code @suggestion.Amount" class="img-fluid" style="max-height: 220px;" />
<div class="small text-muted text-break">
<a href="@suggestion.QrCodeUrl" target="_blank" rel="noopener noreferrer">Open payment link</a>
@@ -207,4 +207,11 @@ public partial class RegistryContributionAmount : ComponentBase
public string QrCodeUrl { get; init; } = string.Empty;
public int RepeatCount { get; init; }
}
protected string GetCurrencySymbol()
{
return string.IsNullOrWhiteSpace(Registry?.CurrencyCode)
? "€"
: Registry.CurrencyCode;
}
}
@@ -87,7 +87,18 @@ else
}
@if (item.ParticipationAllowed && GetParticipationTotalAmount(item).HasValue)
{
<p class="mb-2"><strong>Participation:</strong> €@item.MoneyFulfilledAmount.ToString("0.00") out of €@GetParticipationTotalAmount(item)!.Value.ToString("0.00") fulfilled</p>
<p class="mb-2">
<strong>Participation:</strong>
@item.CurrencyCode@item.MoneyFulfilledAmount.ToString("0.00")
@if (GetParticipationTotalAmount(item)!.Value > 0)
{
<span> out of @item.CurrencyCode@GetParticipationTotalAmount(item)!.Value.ToString("0.00") fulfilled</span>
}
else
{
<span> fulfilled</span>
}
</p>
}
@if (item.Purchasers.Count > 0 || item.Contributors.Count > 0)
@@ -31,7 +31,7 @@ public sealed class RegistryItemEditModel
public string? ProductUrl { get; set; }
public string? Description { get; set; }
public decimal? PriceAmount { get; set; }
public string CurrencyCode { get; set; } = "EUR";
public string CurrencyCode { get; set; } = "";
public int DesiredQuantity { get; set; } = 1;
public bool ParticipationAllowed { get; set; }
public decimal? ParticipationTargetAmount { get; set; }
@@ -55,7 +55,7 @@ public sealed class RegistrySettingsEditModel
public DateOnly? BirthDate { get; set; }
public string? HeaderContentHtml { get; set; }
public string? ShippingAddress { get; set; }
public string CurrencyCode { get; set; } = "EUR";
public string CurrencyCode { get; set; } = "";
public string ThemeKey { get; set; } = "default";
public string? BankAccountIban { get; set; }
public string? BankAccountBic { get; set; }
@@ -82,7 +82,7 @@ public sealed class RegistryPublicViewModel
public string? BabyName { get; init; }
public string? HeaderContentHtml { get; init; }
public string? ShippingAddress { get; init; }
public string CurrencyCode { get; init; } = "EUR";
public string CurrencyCode { get; init; } = "";
public string ThemeKey { get; init; } = "default";
public RegistryType RegistryType { get; init; }
public string? BankAccountIban { get; init; }
@@ -117,7 +117,7 @@ public sealed class RegistryPublicItemViewModel
public string? ProductUrl { get; init; }
public string? Description { get; init; }
public decimal? PriceAmount { get; init; }
public string CurrencyCode { get; init; } = "EUR";
public string CurrencyCode { get; init; } = "";
public int DesiredQuantity { get; init; }
public int PurchasedQuantity { get; init; }
public bool ParticipationAllowed { get; init; }
@@ -11,6 +11,7 @@ namespace BirthList.Web.Features.Registries;
internal sealed class RegistryService(RegistryDbContext registryDbContext, ApplicationDbContext applicationDbContext)
{
private const string DefaultCategoryName = "General";
private const string DefaultCurrencySymbol = "€";
public async Task<Guid> CreateRegistryAsync(string userId, RegistryCreateModel model, CancellationToken cancellationToken)
{
@@ -31,7 +32,7 @@ internal sealed class RegistryService(RegistryDbContext registryDbContext, Appli
ThemeKey = string.IsNullOrWhiteSpace(model.ThemeKey) ? "default" : model.ThemeKey.Trim(),
PublicLinkCode = publicCode,
CreatedAtUtc = DateTimeOffset.UtcNow,
CurrencyCode = "EUR"
CurrencyCode = DefaultCurrencySymbol
};
registryDbContext.Registries.Add(registry);
@@ -373,7 +374,7 @@ internal sealed class RegistryService(RegistryDbContext registryDbContext, Appli
registry.BirthDate = model.BirthDate;
registry.HeaderContentHtml = string.IsNullOrWhiteSpace(model.HeaderContentHtml) ? null : model.HeaderContentHtml;
registry.ShippingAddress = string.IsNullOrWhiteSpace(model.ShippingAddress) ? null : model.ShippingAddress.Trim();
registry.CurrencyCode = string.IsNullOrWhiteSpace(model.CurrencyCode) ? "EUR" : model.CurrencyCode.Trim().ToUpperInvariant();
registry.CurrencyCode = NormalizeCurrencySymbol(model.CurrencyCode);
registry.ThemeKey = string.IsNullOrWhiteSpace(model.ThemeKey) ? "default" : model.ThemeKey.Trim();
settings.BankAccountIban = string.IsNullOrWhiteSpace(model.BankAccountIban) ? null : model.BankAccountIban.Trim();
@@ -386,62 +387,6 @@ internal sealed class RegistryService(RegistryDbContext registryDbContext, Appli
await registryDbContext.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
}
private static IReadOnlyList<ContributionAmountQrCodeModel> ParseContributionAmountQrCodes(string? json)
{
if (string.IsNullOrWhiteSpace(json))
{
return [];
}
try
{
var items = JsonSerializer.Deserialize<List<ContributionAmountQrCodeModel>>(json);
if (items is null)
{
return [];
}
return items
.Where(x => x.Amount > 0 && !string.IsNullOrWhiteSpace(x.QrCodeUrl))
.Select(x => new ContributionAmountQrCodeModel
{
Amount = x.Amount,
QrCodeUrl = x.QrCodeUrl.Trim()
})
.OrderBy(x => x.Amount)
.ToList();
}
catch (JsonException)
{
return [];
}
}
private static string? SerializeContributionAmountQrCodes(IEnumerable<ContributionAmountQrCodeModel>? amountQrCodes)
{
if (amountQrCodes is null)
{
return null;
}
var normalized = amountQrCodes
.Where(x => x.Amount > 0 && !string.IsNullOrWhiteSpace(x.QrCodeUrl))
.Select(x => new ContributionAmountQrCodeModel
{
Amount = x.Amount,
QrCodeUrl = x.QrCodeUrl.Trim()
})
.OrderBy(x => x.Amount)
.ToList();
if (normalized.Count == 0)
{
return null;
}
return JsonSerializer.Serialize(normalized);
}
public async Task<IReadOnlyList<RegistryItemEditModel>> GetRegistryItemsAsync(Guid registryId, CancellationToken cancellationToken)
{
var defaultCategory = await EnsureDefaultCategoryAsync(registryId, cancellationToken).ConfigureAwait(false);
@@ -682,7 +627,7 @@ internal sealed class RegistryService(RegistryDbContext registryDbContext, Appli
entity.ProductUrl = string.IsNullOrWhiteSpace(model.ProductUrl) ? null : model.ProductUrl.Trim();
entity.Description = string.IsNullOrWhiteSpace(model.Description) ? null : model.Description.Trim();
entity.PriceAmount = model.PriceAmount;
entity.CurrencyCode = string.IsNullOrWhiteSpace(model.CurrencyCode) ? "EUR" : model.CurrencyCode.Trim().ToUpperInvariant();
entity.CurrencyCode = NormalizeCurrencySymbol(model.CurrencyCode);
entity.DesiredQuantity = model.DesiredQuantity < 1 ? 1 : model.DesiredQuantity;
entity.ParticipationAllowed = model.ParticipationAllowed;
entity.ParticipationTargetAmount = model.ParticipationAllowed ? model.ParticipationTargetAmount : null;
@@ -1629,4 +1574,67 @@ internal sealed class RegistryService(RegistryDbContext registryDbContext, Appli
await registryDbContext.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
}
}
private static IReadOnlyList<ContributionAmountQrCodeModel> ParseContributionAmountQrCodes(string? json)
{
if (string.IsNullOrWhiteSpace(json))
{
return [];
}
try
{
var items = JsonSerializer.Deserialize<List<ContributionAmountQrCodeModel>>(json);
if (items is null)
{
return [];
}
return items
.Where(x => x.Amount > 0 && !string.IsNullOrWhiteSpace(x.QrCodeUrl))
.Select(x => new ContributionAmountQrCodeModel
{
Amount = x.Amount,
QrCodeUrl = x.QrCodeUrl.Trim()
})
.OrderBy(x => x.Amount)
.ToList();
}
catch (JsonException)
{
return [];
}
}
private static string? SerializeContributionAmountQrCodes(IEnumerable<ContributionAmountQrCodeModel>? amountQrCodes)
{
if (amountQrCodes is null)
{
return null;
}
var normalized = amountQrCodes
.Where(x => x.Amount > 0 && !string.IsNullOrWhiteSpace(x.QrCodeUrl))
.Select(x => new ContributionAmountQrCodeModel
{
Amount = x.Amount,
QrCodeUrl = x.QrCodeUrl.Trim()
})
.OrderBy(x => x.Amount)
.ToList();
if (normalized.Count == 0)
{
return null;
}
return JsonSerializer.Serialize(normalized);
}
private static string NormalizeCurrencySymbol(string? currencySymbol)
{
return string.IsNullOrWhiteSpace(currencySymbol)
? DefaultCurrencySymbol
: currencySymbol.Trim();
}
}