Add localization support and UI enhancements
Build and Push Docker Image / build-and-push (push) Successful in 27s
Build and Push Docker Image / build-and-push (push) Successful in 27s
Introduced centralized RESX localization with hierarchical keys for multi-language support. Added resource files for English, French, Dutch (Belgium), and pseudo-localization. Replaced hardcoded strings in Razor components with localized string references. Added a `PreferredCulture` property to `ApplicationUser` with a migration to store user-specific culture preferences. Implemented `LocalizationService` to manage supported cultures and user preferences. Added a language picker in `TopBar.razor` and configured `RequestLocalizationOptions` for culture detection. Localized registry-related components and error messages. Enhanced UI elements to dynamically display localized content. Added pseudo-localization to identify hardcoded strings during development.
This commit is contained in:
@@ -1,27 +1,26 @@
|
||||
@page "/Error"
|
||||
@using System.Diagnostics
|
||||
|
||||
<PageTitle>Error</PageTitle>
|
||||
<PageTitle>@L["Error.PageTitle"]</PageTitle>
|
||||
|
||||
<h1 class="text-danger">Error.</h1>
|
||||
<h2 class="text-danger">An error occurred while processing your request.</h2>
|
||||
<h1 class="text-danger">@L["Error.Title"]</h1>
|
||||
<h2 class="text-danger">@L["Error.Subtitle"]</h2>
|
||||
|
||||
@if (ShowRequestId)
|
||||
{
|
||||
<p>
|
||||
<strong>Request ID:</strong> <code>@RequestId</code>
|
||||
<strong>@L["Error.RequestId"]</strong> <code>@RequestId</code>
|
||||
</p>
|
||||
}
|
||||
|
||||
<h3>Development Mode</h3>
|
||||
<h3>@L["Error.DevMode"]</h3>
|
||||
<p>
|
||||
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
|
||||
@L["Error.DevHint1"]
|
||||
</p>
|
||||
<p>
|
||||
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
|
||||
It can result in displaying sensitive information from exceptions to end users.
|
||||
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
|
||||
and restarting the app.
|
||||
<strong>@L["Error.DevHint2"]</strong>
|
||||
@L["Error.DevHint3"]
|
||||
@L["Error.DevHint4"]
|
||||
</p>
|
||||
|
||||
@code{
|
||||
|
||||
@@ -2,23 +2,23 @@
|
||||
|
||||
@using BirthList.Web.Features.Registries
|
||||
|
||||
<PageTitle>Birth Registry</PageTitle>
|
||||
<PageTitle>@L["Home.PageTitle"]</PageTitle>
|
||||
|
||||
<h1>Welcome to Gift List</h1>
|
||||
<h1>@L["Home.Welcome"]</h1>
|
||||
|
||||
<AuthorizeView>
|
||||
<Authorized Context="authState">
|
||||
<div class="registry-sections">
|
||||
<div class="mb-4">
|
||||
<div class="section-header">
|
||||
<h2>Registries you manage</h2>
|
||||
<h2>@L["Home.ManagedRegistries"]</h2>
|
||||
<button class="btn btn-primary btn-sm" @onclick="() => ShowCreateForm = !ShowCreateForm">
|
||||
<span class="bi bi-plus"></span> Create new
|
||||
<span class="bi bi-plus"></span> @L["Home.CreateNew"]
|
||||
</button>
|
||||
</div>
|
||||
@if (MyRegistries.Count == 0)
|
||||
{
|
||||
<p class="text-muted">No registries yet.</p>
|
||||
<p class="text-muted">@L["Home.NoRegistries"]</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -28,8 +28,8 @@
|
||||
<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>
|
||||
<a href="/registry/@registry.PublicLinkCode" class="btn btn-outline-primary btn-sm">@L["Home.View"]</a>
|
||||
<a href="/registry/@registry.Id/admin" class="btn btn-outline-secondary btn-sm">@L["Home.Manage"]</a>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -38,10 +38,10 @@
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<h2>Visited registries</h2>
|
||||
<h2>@L["Home.VisitedRegistries"]</h2>
|
||||
@if (VisitedRegistries.Count == 0)
|
||||
{
|
||||
<p class="text-muted">No visited registries yet.</p>
|
||||
<p class="text-muted">@L["Home.NoVisitedRegistries"]</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -51,7 +51,7 @@
|
||||
<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.PublicLinkCode" class="btn btn-outline-primary btn-sm">@L["Home.View"]</a>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -65,30 +65,30 @@
|
||||
<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>
|
||||
<h3 class="modal-title">@L["Home.CreateRegistryTitle"]</h3>
|
||||
<button type="button" class="btn-close" @onclick="() => ShowCreateForm = false"></button>
|
||||
</div>
|
||||
<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>
|
||||
<label class="form-label">@L["Home.Title"]</label>
|
||||
<InputText class="form-control" @bind-Value="Model.Title" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Type</label>
|
||||
<label class="form-label">@L["Home.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>
|
||||
<option value="@BirthList.Domain.Entities.RegistryType.Birth">@L["Home.RegistryType.Birth"]</option>
|
||||
<option value="@BirthList.Domain.Entities.RegistryType.Wedding">@L["Home.RegistryType.Wedding"]</option>
|
||||
<option value="@BirthList.Domain.Entities.RegistryType.Birthday">@L["Home.RegistryType.Birthday"]</option>
|
||||
</InputSelect>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Theme</label>
|
||||
<label class="form-label">@L["Home.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>
|
||||
<option value="default">@L["Home.Theme.Default"]</option>
|
||||
<option value="soft">@L["Home.Theme.Soft"]</option>
|
||||
<option value="modern">@L["Home.Theme.Modern"]</option>
|
||||
</InputSelect>
|
||||
</div>
|
||||
@if (!string.IsNullOrWhiteSpace(ErrorMessage))
|
||||
@@ -96,8 +96,8 @@
|
||||
<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>
|
||||
<button type="button" class="btn btn-outline-secondary" @onclick="() => ShowCreateForm = false">@L["Common.Cancel"]</button>
|
||||
<button class="btn btn-primary" type="submit">@L["Home.CreateNew"]</button>
|
||||
</div>
|
||||
</EditForm>
|
||||
</div>
|
||||
@@ -107,7 +107,9 @@
|
||||
</Authorized>
|
||||
<NotAuthorized>
|
||||
<div class="alert alert-info mt-4">
|
||||
<p>Please <a href="Account/Login">log in</a> to create and manage registries.</p>
|
||||
<p>
|
||||
@L["Home.LoginPromptPrefix"]<a href="Account/Login">@L["Home.LoginPromptLinkText"]</a>@L["Home.LoginPromptSuffix"]
|
||||
</p>
|
||||
</div>
|
||||
</NotAuthorized>
|
||||
</AuthorizeView>
|
||||
@@ -136,13 +138,13 @@
|
||||
var userId = await RegistryUserContext.GetUserIdAsync(CancellationToken.None).ConfigureAwait(false);
|
||||
if (string.IsNullOrWhiteSpace(userId))
|
||||
{
|
||||
ErrorMessage = "You must be logged in to create a registry.";
|
||||
ErrorMessage = L["Home.MustBeLoggedIn"];
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(Model.Title))
|
||||
{
|
||||
ErrorMessage = "Title is required.";
|
||||
ErrorMessage = L["Home.TitleRequired"];
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,24 +4,24 @@
|
||||
@using BirthList.Web.Features.Registries
|
||||
@using BirthList.Web.Authorization
|
||||
|
||||
<PageTitle>Action Log - Registry Admin</PageTitle>
|
||||
<PageTitle>@L["RegistryActionLog.PageTitle"]</PageTitle>
|
||||
|
||||
@if (!IsAuthorized)
|
||||
{
|
||||
<p>Access denied.</p>
|
||||
<p>@L["Common.AccessDenied"]</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<h1>Registry Action Log</h1>
|
||||
<p class="text-muted">This log shows all user actions on this registry: purchases, contributions, and other interactions.</p>
|
||||
<h1>@L["RegistryActionLog.Title"]</h1>
|
||||
<p class="text-muted">@L["RegistryActionLog.Description"]</p>
|
||||
|
||||
@if (ActionLogs is null)
|
||||
{
|
||||
<p>Loading...</p>
|
||||
<p>@L["Common.Loading"]</p>
|
||||
}
|
||||
else if (ActionLogs.Count == 0)
|
||||
{
|
||||
<p class="text-muted">No actions recorded yet.</p>
|
||||
<p class="text-muted">@L["RegistryActionLog.NoActions"]</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -29,13 +29,13 @@ else
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date/Time</th>
|
||||
<th>User</th>
|
||||
<th>Action</th>
|
||||
<th>Item</th>
|
||||
<th>Quantity</th>
|
||||
<th>Amount</th>
|
||||
<th>Details</th>
|
||||
<th>@L["RegistryActionLog.DateTime"]</th>
|
||||
<th>@L["RegistryActionLog.User"]</th>
|
||||
<th>@L["RegistryActionLog.Action"]</th>
|
||||
<th>@L["RegistryActionLog.Item"]</th>
|
||||
<th>@L["RegistryActionLog.Quantity"]</th>
|
||||
<th>@L["RegistryActionLog.Amount"]</th>
|
||||
<th>@L["RegistryActionLog.Details"]</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -48,22 +48,22 @@ else
|
||||
@switch (log.ActionType)
|
||||
{
|
||||
case "RegistryLinkOpened":
|
||||
<span class="badge bg-info">Registry opened</span>
|
||||
<span class="badge bg-info">@L["RegistryActionLog.Badge.RegistryOpened"]</span>
|
||||
break;
|
||||
case "ItemLinkOpened":
|
||||
<span class="badge bg-info">Item link opened</span>
|
||||
<span class="badge bg-info">@L["RegistryActionLog.Badge.ItemLinkOpened"]</span>
|
||||
break;
|
||||
case "MarkPurchased":
|
||||
<span class="badge bg-success">Purchase marked</span>
|
||||
<span class="badge bg-success">@L["RegistryActionLog.Badge.PurchaseMarked"]</span>
|
||||
break;
|
||||
case "UnmarkPurchased":
|
||||
<span class="badge bg-warning">Purchase unmarked</span>
|
||||
<span class="badge bg-warning">@L["RegistryActionLog.Badge.PurchaseUnmarked"]</span>
|
||||
break;
|
||||
case "MarkPartialPurchase":
|
||||
<span class="badge bg-primary">Partial purchase</span>
|
||||
<span class="badge bg-primary">@L["RegistryActionLog.Badge.PartialPurchase"]</span>
|
||||
break;
|
||||
case "LogContribution":
|
||||
<span class="badge bg-secondary">Contribution logged</span>
|
||||
<span class="badge bg-secondary">@L["RegistryActionLog.Badge.ContributionLogged"]</span>
|
||||
break;
|
||||
default:
|
||||
<span class="badge bg-dark">@log.ActionType</span>
|
||||
|
||||
@@ -3,20 +3,20 @@
|
||||
|
||||
@using Blazored.TextEditor
|
||||
|
||||
<PageTitle>Registry Admin</PageTitle>
|
||||
<PageTitle>@L["RegistryAdmin.PageTitle"]</PageTitle>
|
||||
|
||||
@if (!IsAuthorized)
|
||||
{
|
||||
<p>Access denied.</p>
|
||||
<p>@L["Common.AccessDenied"]</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<h1>Registry Admin</h1>
|
||||
<h1>@L["RegistryAdmin.Title"]</h1>
|
||||
|
||||
@if (!IsSmtpConfigured)
|
||||
{
|
||||
<div class="alert alert-warning" role="alert">
|
||||
SMTP is not configured. Email features (identity emails and admin invite emails) are disabled. Configure the Smtp section in appsettings or user secrets.
|
||||
@L["RegistryAdmin.SmtpNotConfigured"]
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ else
|
||||
role="tab"
|
||||
aria-controls="items-content"
|
||||
aria-selected="@(ActiveTab == "items" ? "true" : "false")">
|
||||
<span class="bi bi-box-seam"></span> Items
|
||||
<span class="bi bi-box-seam"></span> @L["RegistryAdmin.Tab.Items"]
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
@@ -40,7 +40,7 @@ else
|
||||
role="tab"
|
||||
aria-controls="settings-content"
|
||||
aria-selected="@(ActiveTab == "settings" ? "true" : "false")">
|
||||
<span class="bi bi-gear"></span> Settings
|
||||
<span class="bi bi-gear"></span> @L["RegistryAdmin.Tab.Settings"]
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
@@ -51,7 +51,7 @@ else
|
||||
role="tab"
|
||||
aria-controls="admins-content"
|
||||
aria-selected="@(ActiveTab == "admins" ? "true" : "false")">
|
||||
<span class="bi bi-people"></span> Administrators
|
||||
<span class="bi bi-people"></span> @L["RegistryAdmin.Tab.Administrators"]
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
@@ -62,12 +62,12 @@ else
|
||||
role="tab"
|
||||
aria-controls="addresses-content"
|
||||
aria-selected="@(ActiveTab == "addresses" ? "true" : "false")">
|
||||
<span class="bi bi-house"></span> Addresses
|
||||
<span class="bi bi-house"></span> @L["RegistryAdmin.Tab.Addresses"]
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<a href="/registry/@RegistryId/admin/action-log" class="nav-link">
|
||||
<span class="bi bi-clock-history"></span> Action Log
|
||||
<span class="bi bi-clock-history"></span> @L["RegistryAdmin.Tab.ActionLog"]
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -76,62 +76,62 @@ else
|
||||
<!-- Items Tab -->
|
||||
<div class="tab-pane fade @(GetTabPaneClass("items"))" id="items-content" role="tabpanel" aria-labelledby="items-tab">
|
||||
<section class="mb-4">
|
||||
<h2>Add or edit item</h2>
|
||||
<h2>@L["RegistryAdmin.AddOrEditItem"]</h2>
|
||||
<EditForm Model="ItemModel" OnValidSubmit="SaveItemAsync" FormName="registry-item-form">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Name</label>
|
||||
<label class="form-label">@L["RegistryAdmin.Name"]</label>
|
||||
<InputText class="form-control" @bind-Value="ItemModel.Name" />
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Product URL</label>
|
||||
<label class="form-label">@L["RegistryAdmin.ProductUrl"]</label>
|
||||
<div class="input-group">
|
||||
<InputText class="form-control" @bind-Value="ItemModel.ProductUrl" />
|
||||
<button type="button" class="btn btn-outline-secondary" @onclick="FetchMetadataAsync">Auto fetch</button>
|
||||
<button type="button" class="btn btn-outline-secondary" @onclick="FetchMetadataAsync">@L["RegistryAdmin.AutoFetch"]</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Picture URL</label>
|
||||
<label class="form-label">@L["RegistryAdmin.PictureUrl"]</label>
|
||||
<InputText class="form-control" @bind-Value="ItemModel.PictureUrl" />
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Description</label>
|
||||
<label class="form-label">@L["RegistryAdmin.Description"]</label>
|
||||
<InputTextArea class="form-control" @bind-Value="ItemModel.Description" rows="2" />
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label class="form-label">Price</label>
|
||||
<label class="form-label">@L["RegistryAdmin.Price"]</label>
|
||||
<InputNumber class="form-control" @bind-Value="ItemModel.PriceAmount" />
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label class="form-label">Currency symbol</label>
|
||||
<label class="form-label">@L["RegistryAdmin.CurrencySymbol"]</label>
|
||||
<InputText class="form-control" @bind-Value="ItemModel.CurrencyCode" />
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label class="form-label">Desired qty</label>
|
||||
<label class="form-label">@L["RegistryAdmin.DesiredQty"]</label>
|
||||
<InputNumber class="form-control" @bind-Value="ItemModel.DesiredQuantity" />
|
||||
</div>
|
||||
<div class="col-md-3 d-flex align-items-center gap-2">
|
||||
<InputCheckbox @bind-Value="ItemModel.ParticipationAllowed" />
|
||||
<label>Participation</label>
|
||||
<label>@L["RegistryAdmin.Participation"]</label>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Participation target</label>
|
||||
<label class="form-label">@L["RegistryAdmin.ParticipationTarget"]</label>
|
||||
<InputNumber class="form-control" @bind-Value="ItemModel.ParticipationTargetAmount" />
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Second hand preference</label>
|
||||
<label class="form-label">@L["RegistryAdmin.SecondHandPreference"]</label>
|
||||
<InputSelect class="form-select" @bind-Value="ItemModel.PreferSecondHand">
|
||||
<option value="">Second hand optional</option>
|
||||
<option value="true">Prefer second hand</option>
|
||||
<option value="false">New only</option>
|
||||
<option value="">@L["RegistryAdmin.SecondHandOptional"]</option>
|
||||
<option value="true">@L["RegistryAdmin.PreferSecondHand"]</option>
|
||||
<option value="false">@L["RegistryAdmin.NewOnly"]</option>
|
||||
</InputSelect>
|
||||
</div>
|
||||
<div class="col-md-3 d-flex align-items-center gap-2">
|
||||
<InputCheckbox @bind-Value="ItemModel.IsGiven" />
|
||||
<label>Given</label>
|
||||
<label>@L["RegistryAdmin.Given"]</label>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Category</label>
|
||||
<label class="form-label">@L["RegistryAdmin.Category"]</label>
|
||||
<InputSelect class="form-select" @bind-Value="ItemModel.CategoryId">
|
||||
@foreach (var category in ItemCategories)
|
||||
{
|
||||
@@ -140,16 +140,16 @@ else
|
||||
</InputSelect>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-primary mt-3" type="submit">Save item</button>
|
||||
<button class="btn btn-primary mt-3" type="submit">@L["RegistryAdmin.SaveItem"]</button>
|
||||
</EditForm>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="d-flex justify-content-between align-items-end flex-wrap gap-3">
|
||||
<h2 class="mb-0">Categories and items</h2>
|
||||
<h2 class="mb-0">@L["RegistryAdmin.CategoriesAndItems"]</h2>
|
||||
<div class="d-flex gap-2">
|
||||
<InputText class="form-control" @bind-Value="NewCategoryName" placeholder="New category" />
|
||||
<button type="button" class="btn btn-outline-primary" @onclick="AddCategoryAsync">Add category</button>
|
||||
<InputText class="form-control" @bind-Value="NewCategoryName" placeholder="@L["RegistryAdmin.NewCategory"]" />
|
||||
<button type="button" class="btn btn-outline-primary" @onclick="AddCategoryAsync">@L["RegistryAdmin.AddCategory"]</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -158,7 +158,7 @@ else
|
||||
<div class="alert alert-warning mt-3 mb-0" role="alert">@ItemManagementMessage</div>
|
||||
}
|
||||
|
||||
<p class="text-muted mt-3 mb-2">Drag categories or items to reorder. Drop items into another category to regroup them.</p>
|
||||
<p class="text-muted mt-3 mb-2">@L["RegistryAdmin.DragHint"]</p>
|
||||
|
||||
<div class="category-groups mt-3">
|
||||
@foreach (var category in ItemCategories)
|
||||
@@ -178,8 +178,8 @@ else
|
||||
{
|
||||
<div class="d-flex gap-2 align-items-center w-100">
|
||||
<InputText class="form-control form-control-sm" @bind-Value="CategoryRenameName" />
|
||||
<button type="button" class="btn btn-sm btn-primary" @onclick="() => SaveCategoryRenameAsync(category.Id)">Save</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary" @onclick="CancelCategoryRename">Cancel</button>
|
||||
<button type="button" class="btn btn-sm btn-primary" @onclick="() => SaveCategoryRenameAsync(category.Id)">@L["Common.Save"]</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary" @onclick="CancelCategoryRename">@L["Common.Cancel"]</button>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
@@ -189,13 +189,13 @@ else
|
||||
<button type="button"
|
||||
class="btn btn-sm btn-outline-secondary"
|
||||
@onclick="() => StartCategoryRename(category)">
|
||||
Rename
|
||||
@L["RegistryAdmin.Rename"]
|
||||
</button>
|
||||
<button type="button"
|
||||
class="btn btn-sm btn-outline-danger"
|
||||
@onclick="() => RemoveCategoryAsync(category.Id)"
|
||||
disabled="@(ItemCategories.Count <= 1)">
|
||||
Remove
|
||||
@L["Common.Remove"]
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
@@ -208,17 +208,17 @@ else
|
||||
@ondrop="() => OnCategoryItemsDropAsync(category.Id)">
|
||||
@if (category.Items.Count == 0)
|
||||
{
|
||||
<div class="p-3 text-muted">Drop items here.</div>
|
||||
<div class="p-3 text-muted">@L["RegistryAdmin.DropItemsHere"]</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<table class="table table-striped mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Desired Qty</th>
|
||||
<th>Participation</th>
|
||||
<th>Purchased by / Contributed by</th>
|
||||
<th>@L["RegistryAdmin.Header.Name"]</th>
|
||||
<th>@L["RegistryAdmin.Header.DesiredQty"]</th>
|
||||
<th>@L["RegistryAdmin.Header.Participation"]</th>
|
||||
<th>@L["RegistryAdmin.Header.PurchasedContributedBy"]</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -239,12 +239,12 @@ else
|
||||
@ondrop="() => OnItemDropAsync(category.Id, item.Id!.Value)">
|
||||
<td>@item.Name</td>
|
||||
<td>@item.DesiredQuantity</td>
|
||||
<td>@(item.ParticipationAllowed ? "Yes" : "No")</td>
|
||||
<td>@(item.ParticipationAllowed ? L["Common.Yes"].Value : L["Common.No"].Value)</td>
|
||||
<td>
|
||||
@if (item.Purchasers.Count > 0)
|
||||
{
|
||||
<div class="mb-1">
|
||||
<small><strong>Purchased:</strong></small>
|
||||
<small><strong>@L["RegistryAdmin.Purchased"]</strong></small>
|
||||
<div class="small">
|
||||
@foreach (var purchaser in item.Purchasers)
|
||||
{
|
||||
@@ -256,7 +256,7 @@ else
|
||||
@if (item.Contributors.Count > 0)
|
||||
{
|
||||
<div>
|
||||
<small><strong>Contributed:</strong></small>
|
||||
<small><strong>@L["RegistryAdmin.Contributed"]</strong></small>
|
||||
<div class="small">
|
||||
@foreach (var contributor in item.Contributors)
|
||||
{
|
||||
@@ -267,8 +267,8 @@ else
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-outline-secondary me-2" @onclick="() => EditItem(item)">Edit</button>
|
||||
<button class="btn btn-sm btn-outline-danger" @onclick="() => DeleteItemAsync(item.Id)">Delete</button>
|
||||
<button class="btn btn-sm btn-outline-secondary me-2" @onclick="() => EditItem(item)">@L["Common.Edit"]</button>
|
||||
<button class="btn btn-sm btn-outline-danger" @onclick="() => DeleteItemAsync(item.Id)">@L["Common.Remove"]</button>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
@@ -292,57 +292,57 @@ else
|
||||
<!-- Settings Tab -->
|
||||
<div class="tab-pane fade @(GetTabPaneClass("settings"))" id="settings-content" role="tabpanel" aria-labelledby="settings-tab">
|
||||
<section class="mb-4">
|
||||
<h2>Registry Settings</h2>
|
||||
<h2>@L["RegistryAdmin.RegistrySettings"]</h2>
|
||||
<EditForm Model="SettingsModel" OnValidSubmit="SaveSettingsAsync" FormName="registry-settings-form">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Baby name</label>
|
||||
<label class="form-label">@L["RegistryAdmin.BabyName"]</label>
|
||||
<InputText class="form-control" @bind-Value="SettingsModel.BabyName" />
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Birth date</label>
|
||||
<label class="form-label">@L["RegistryAdmin.BirthDate"]</label>
|
||||
<InputDate class="form-control" @bind-Value="SettingsModel.BirthDate" />
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label class="form-label">Currency symbol</label>
|
||||
<label class="form-label">@L["RegistryAdmin.CurrencySymbol"]</label>
|
||||
<InputText class="form-control" @bind-Value="SettingsModel.CurrencyCode" />
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Theme</label>
|
||||
<label class="form-label">@L["RegistryAdmin.Theme"]</label>
|
||||
<InputSelect class="form-select" @bind-Value="SettingsModel.ThemeKey">
|
||||
<option value="default">Default</option>
|
||||
<option value="soft">Soft</option>
|
||||
<option value="modern">Modern</option>
|
||||
<option value="default">@L["RegistryAdmin.Theme.Default"]</option>
|
||||
<option value="soft">@L["RegistryAdmin.Theme.Soft"]</option>
|
||||
<option value="modern">@L["RegistryAdmin.Theme.Modern"]</option>
|
||||
</InputSelect>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<label class="form-label">Shipping address</label>
|
||||
<label class="form-label">@L["RegistryAdmin.ShippingAddress"]</label>
|
||||
<InputTextArea class="form-control" @bind-Value="SettingsModel.ShippingAddress" rows="4" />
|
||||
<small class="form-text text-muted">Line breaks will be preserved</small>
|
||||
<small class="form-text text-muted">@L["RegistryAdmin.LineBreaksPreserved"]</small>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<label class="form-label">Top content</label>
|
||||
<label class="form-label">@L["RegistryAdmin.TopContent"]</label>
|
||||
<div class="editor-wrapper">
|
||||
<BlazoredTextEditor @ref="TextEditor" Placeholder="Welcome text" Theme="snow" ToolbarContent="@ToolbarContent" />
|
||||
<BlazoredTextEditor @ref="TextEditor" Placeholder="@L["RegistryAdmin.WelcomeText"]" Theme="snow" ToolbarContent="@ToolbarContent" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<h3>Bank Account Settings</h3>
|
||||
<h3>@L["RegistryAdmin.BankAccountSettings"]</h3>
|
||||
<div class="row g-3 mt-2">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Bank account name</label>
|
||||
<label class="form-label">@L["RegistryAdmin.BankAccountName"]</label>
|
||||
<InputText class="form-control" @bind-Value="SettingsModel.BankAccountDisplayName" />
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">IBAN</label>
|
||||
<label class="form-label">@L["RegistryPublic.IBAN"]</label>
|
||||
<InputText class="form-control" @bind-Value="SettingsModel.BankAccountIban" />
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">BIC</label>
|
||||
<label class="form-label">@L["RegistryPublic.BIC"]</label>
|
||||
<InputText class="form-control" @bind-Value="SettingsModel.BankAccountBic" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -350,31 +350,31 @@ else
|
||||
<div class="form-check">
|
||||
<InputCheckbox class="form-check-input" @bind-Value="SettingsModel.ShowBankAccountName" id="showBankName" />
|
||||
<label class="form-check-label" for="showBankName">
|
||||
Display bank account name
|
||||
@L["RegistryAdmin.DisplayBankAccountName"]
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<h3>Contribution Payment Options</h3>
|
||||
<h3>@L["RegistryAdmin.ContributionPaymentOptions"]</h3>
|
||||
<div class="row g-3 mt-2">
|
||||
<div class="col-md-12">
|
||||
<label class="form-label">Single QR code URL</label>
|
||||
<label class="form-label">@L["RegistryAdmin.SingleQrCodeUrl"]</label>
|
||||
<InputText class="form-control" @bind-Value="SettingsModel.ContributionQrCodeUrl" />
|
||||
<small class="form-text text-muted">Optional: one QR code that donors can scan for any amount.</small>
|
||||
<small class="form-text text-muted">@L["RegistryAdmin.SingleQrHelp"]</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<h4 class="mb-0">Amount-specific QR codes</h4>
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" @onclick="AddContributionAmountQrCode">Add QR amount</button>
|
||||
<h4 class="mb-0">@L["RegistryAdmin.AmountSpecificQrCodes"]</h4>
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" @onclick="AddContributionAmountQrCode">@L["RegistryAdmin.AddQrAmount"]</button>
|
||||
</div>
|
||||
|
||||
@if (SettingsModel.ContributionAmountQrCodes.Count == 0)
|
||||
{
|
||||
<p class="text-muted mt-2 mb-0">No amount-specific QR codes configured.</p>
|
||||
<p class="text-muted mt-2 mb-0">@L["RegistryAdmin.NoAmountSpecificQrCodes"]</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -383,15 +383,15 @@ else
|
||||
{
|
||||
<div class="row g-2 align-items-end">
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Amount</label>
|
||||
<label class="form-label">@L["Common.Amount"]</label>
|
||||
<InputNumber class="form-control" @bind-Value="amountQr.Amount" />
|
||||
</div>
|
||||
<div class="col-md-7">
|
||||
<label class="form-label">QR code URL</label>
|
||||
<label class="form-label">@L["RegistryAdmin.QrCodeUrl"]</label>
|
||||
<InputText class="form-control" @bind-Value="amountQr.QrCodeUrl" />
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<button type="button" class="btn btn-outline-danger w-100" @onclick="() => RemoveContributionAmountQrCode(amountQr)">Remove</button>
|
||||
<button type="button" class="btn btn-outline-danger w-100" @onclick="() => RemoveContributionAmountQrCode(amountQr)">@L["Common.Remove"]</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -400,7 +400,7 @@ else
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-primary mt-4" type="submit">Save settings</button>
|
||||
<button class="btn btn-primary mt-4" type="submit">@L["RegistryAdmin.SaveSettings"]</button>
|
||||
</EditForm>
|
||||
</section>
|
||||
</div>
|
||||
@@ -408,19 +408,19 @@ else
|
||||
<!-- Addresses Tab -->
|
||||
<div class="tab-pane fade @(GetTabPaneClass("addresses"))" id="addresses-content" role="tabpanel" aria-labelledby="addresses-tab">
|
||||
<section class="mb-4">
|
||||
<h2>User addresses</h2>
|
||||
<h2>@L["RegistryAdmin.UserAddresses"]</h2>
|
||||
@if (AccessibleUserAddresses.Count == 0)
|
||||
{
|
||||
<p class="text-muted">No users found yet.</p>
|
||||
<p class="text-muted">@L["RegistryAdmin.NoUsersFound"]</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Email</th>
|
||||
<th>Address</th>
|
||||
<th>@L["RegistryAdmin.Name"]</th>
|
||||
<th>@L["RegistryAdmin.Email"]</th>
|
||||
<th>@L["RegistryAdmin.Address"]</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -441,17 +441,17 @@ else
|
||||
<!-- Admins Tab -->
|
||||
<div class="tab-pane fade @(GetTabPaneClass("admins"))" id="admins-content" role="tabpanel" aria-labelledby="admins-tab">
|
||||
<section class="mb-4">
|
||||
<h2>Current administrators</h2>
|
||||
<h2>@L["RegistryAdmin.CurrentAdministrators"]</h2>
|
||||
@if (Admins.Count == 0)
|
||||
{
|
||||
<p class="text-muted">No admins assigned yet.</p>
|
||||
<p class="text-muted">@L["RegistryAdmin.NoAdmins"]</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Email / Name</th>
|
||||
<th>@L["RegistryAdmin.EmailOrName"]</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -461,7 +461,7 @@ else
|
||||
<tr>
|
||||
<td>@admin.DisplayName</td>
|
||||
<td class="text-end">
|
||||
<button class="btn btn-sm btn-outline-danger" @onclick="() => DeleteAdminAsync(admin.UserId)">Remove</button>
|
||||
<button class="btn btn-sm btn-outline-danger" @onclick="() => DeleteAdminAsync(admin.UserId)">@L["Common.Remove"]</button>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
@@ -471,19 +471,19 @@ else
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Invite administrator</h2>
|
||||
<h2>@L["RegistryAdmin.InviteAdministrator"]</h2>
|
||||
<div class="row g-2">
|
||||
<div class="col-md-8">
|
||||
<InputText class="form-control" @bind-Value="InviteEmail" placeholder="optional email" />
|
||||
<InputText class="form-control" @bind-Value="InviteEmail" placeholder="@L["RegistryAdmin.OptionalEmail"]" />
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<button class="btn btn-outline-primary w-100" @onclick="CreateInviteAsync">Create invite</button>
|
||||
<button class="btn btn-outline-primary w-100" @onclick="CreateInviteAsync">@L["RegistryAdmin.CreateInvite"]</button>
|
||||
</div>
|
||||
</div>
|
||||
@if (!string.IsNullOrWhiteSpace(InviteLink))
|
||||
{
|
||||
<p class="mt-3">
|
||||
<strong>Invite link:</strong>
|
||||
<strong>@L["RegistryAdmin.InviteLink"]</strong>
|
||||
<br />
|
||||
<a href="@InviteLink" target="_blank">@InviteLink</a>
|
||||
</p>
|
||||
|
||||
@@ -3,29 +3,29 @@
|
||||
|
||||
@using BirthList.Web.Features.Registries
|
||||
|
||||
<PageTitle>Contribution Amount</PageTitle>
|
||||
<PageTitle>@L["RegistryContributionAmount.PageTitle"]</PageTitle>
|
||||
|
||||
@if (Registry is null || Item is null)
|
||||
{
|
||||
<p>Item not found.</p>
|
||||
<p>@L["RegistryContributionAmount.ItemNotFound"]</p>
|
||||
}
|
||||
else if (!IsAuthenticated)
|
||||
{
|
||||
<p>Please log in first.</p>
|
||||
<p>@L["RegistryContributionAmount.LoginFirst"]</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="registry-shell @RegistryThemeService.GetCssClass(Registry.RegistryType, Registry.ThemeKey)">
|
||||
<h1>Partially fulfill: @Item.Name</h1>
|
||||
<h1>@L["RegistryContributionAmount.PartiallyFulfill", Item.Name]</h1>
|
||||
|
||||
<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 @GetCurrencySymbol()200.</p>
|
||||
<p class="text-muted mb-0">@L["RegistryContributionAmount.NoRepresentableAmount", GetCurrencySymbol()]</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<label class="form-label">Select amount: @GetCurrencySymbol()@SelectedAmount</label>
|
||||
<label class="form-label">@L["RegistryContributionAmount.SelectAmount", GetCurrencySymbol(), SelectedAmount]</label>
|
||||
<input type="range"
|
||||
class="form-range"
|
||||
min="0"
|
||||
@@ -37,11 +37,11 @@ else
|
||||
|
||||
@if (Suggestions.Count == 0)
|
||||
{
|
||||
<p class="text-muted mt-2">No QR combination available for this amount.</p>
|
||||
<p class="text-muted mt-2">@L["RegistryContributionAmount.NoQrCombination"]</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<p class="mt-3 mb-2"><strong>Suggested QR combination:</strong></p>
|
||||
<p class="mt-3 mb-2"><strong>@L["RegistryContributionAmount.SuggestedCombination"]</strong></p>
|
||||
<ul class="mb-3">
|
||||
@foreach (var suggestion in Suggestions)
|
||||
{
|
||||
@@ -58,7 +58,7 @@ else
|
||||
<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>
|
||||
<a href="@suggestion.QrCodeUrl" target="_blank" rel="noopener noreferrer">@L["RegistryContributionAmount.OpenPaymentLink"]</a>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -67,8 +67,8 @@ else
|
||||
}
|
||||
|
||||
<div class="d-flex gap-2 mt-3">
|
||||
<button class="btn btn-success" @onclick="ConfirmAsync" disabled="@(SelectedAmount <= 0)">I transferred this amount</button>
|
||||
<button class="btn btn-outline-secondary" @onclick="BackToRegistry">Back</button>
|
||||
<button class="btn btn-success" @onclick="ConfirmAsync" disabled="@(SelectedAmount <= 0)">@L["RegistryContributionAmount.TransferredAmount"]</button>
|
||||
<button class="btn btn-outline-secondary" @onclick="BackToRegistry">@L["Common.Back"]</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
@page "/registry/{RegistryId:guid}/invite/{Token}"
|
||||
|
||||
<PageTitle>Admin Invite</PageTitle>
|
||||
<PageTitle>@L["RegistryInvite.PageTitle"]</PageTitle>
|
||||
|
||||
<h1>Admin invitation</h1>
|
||||
<h1>@L["RegistryInvite.Title"]</h1>
|
||||
|
||||
@if (Redeemed is null)
|
||||
{
|
||||
<p>Validating invitation...</p>
|
||||
<p>@L["RegistryInvite.Validating"]</p>
|
||||
}
|
||||
else if (Redeemed.Value)
|
||||
{
|
||||
<p>Invitation accepted. You are now an admin.</p>
|
||||
<a class="btn btn-primary" href="/registry/@RegistryId/admin">Go to admin</a>
|
||||
<p>@L["RegistryInvite.Accepted"]</p>
|
||||
<a class="btn btn-primary" href="/registry/@RegistryId/admin">@L["RegistryInvite.GoToAdmin"]</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<p>The invitation is invalid or already used.</p>
|
||||
<p>@L["RegistryInvite.Invalid"]</p>
|
||||
}
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
|
||||
@using BirthList.Web.Features.Registries
|
||||
|
||||
<PageTitle>Registry</PageTitle>
|
||||
<PageTitle>@L["RegistryPublic.PageTitle"]</PageTitle>
|
||||
|
||||
@if (Registry is null)
|
||||
{
|
||||
<p>Registry not found.</p>
|
||||
<p>@L["RegistryPublic.NotFound"]</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -22,7 +22,7 @@ else
|
||||
@if (!string.IsNullOrWhiteSpace(Registry.ShippingAddress))
|
||||
{
|
||||
<div class="alert alert-info">
|
||||
<strong>Shipping address</strong><br />
|
||||
<strong>@L["RegistryPublic.ShippingAddress"]</strong><br />
|
||||
<span class="shipping-address-text">@Registry.ShippingAddress</span>
|
||||
</div>
|
||||
}
|
||||
@@ -30,18 +30,18 @@ else
|
||||
@if (Registry.ShowBankAccountName && !string.IsNullOrWhiteSpace(Registry.BankAccountIban))
|
||||
{
|
||||
<div class="alert alert-secondary">
|
||||
<strong>Bank transfer participation</strong><br />
|
||||
<strong>@L["RegistryPublic.BankTransferParticipation"]</strong><br />
|
||||
@Registry.BankAccountDisplayName<br />
|
||||
IBAN: @Registry.BankAccountIban
|
||||
@L["RegistryPublic.IBAN"]: @Registry.BankAccountIban
|
||||
@if (!string.IsNullOrWhiteSpace(Registry.BankAccountBic))
|
||||
{
|
||||
<span> | BIC: @Registry.BankAccountBic</span>
|
||||
<span> | @L["RegistryPublic.BIC"]: @Registry.BankAccountBic</span>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="category-list">
|
||||
@foreach (var category in Registry.Categories)
|
||||
@foreach (var category in Registry.Categories.Where(x => x.Items.Count > 0))
|
||||
{
|
||||
var isCollapsed = IsCategoryCollapsed(category.Id);
|
||||
<section class="category-section">
|
||||
@@ -66,11 +66,11 @@ else
|
||||
<h5 class="card-title">@item.Name</h5>
|
||||
@if (item.PreferSecondHand == true)
|
||||
{
|
||||
<span class="badge bg-info">Second-hand preferred</span>
|
||||
<span class="badge bg-info">@L["RegistryPublic.SecondHandPreferred"]</span>
|
||||
}
|
||||
else if (item.PreferSecondHand == null)
|
||||
{
|
||||
<span class="badge bg-warning">Second-hand optional</span>
|
||||
<span class="badge bg-warning">@L["RegistryPublic.SecondHandOptional"]</span>
|
||||
}
|
||||
</div>
|
||||
@if (!string.IsNullOrWhiteSpace(item.Description))
|
||||
@@ -79,24 +79,24 @@ else
|
||||
}
|
||||
@if (item.DesiredQuantity > 1)
|
||||
{
|
||||
<p class="mb-1"><strong>Qty:</strong> @item.PurchasedQuantity/@item.DesiredQuantity purchased</p>
|
||||
<p class="mb-1"><strong>@L["RegistryPublic.Qty"]</strong> @item.PurchasedQuantity/@item.DesiredQuantity @L["RegistryPublic.PurchasedSuffix"]</p>
|
||||
}
|
||||
@if (item.PriceAmount.HasValue)
|
||||
{
|
||||
<p class="mb-1"><strong>Price:</strong> @item.PriceAmount.Value.ToString("0.00") @item.CurrencyCode</p>
|
||||
<p class="mb-1"><strong>@L["RegistryPublic.Price"]</strong> @item.PriceAmount.Value.ToString("0.00") @item.CurrencyCode</p>
|
||||
}
|
||||
@if (item.ParticipationAllowed && GetParticipationTotalAmount(item).HasValue)
|
||||
{
|
||||
<p class="mb-2">
|
||||
<strong>Participation:</strong>
|
||||
<strong>@L["RegistryPublic.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>
|
||||
<span> @L["RegistryPublic.OutOfFulfilled", item.CurrencyCode, GetParticipationTotalAmount(item)!.Value.ToString("0.00")]</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span> fulfilled</span>
|
||||
<span> @L["RegistryPublic.FulfilledOnly"]</span>
|
||||
}
|
||||
</p>
|
||||
}
|
||||
@@ -109,7 +109,7 @@ else
|
||||
<div class="mb-2">
|
||||
@if (item.CanViewPurchasers && Registry.IsAdmin)
|
||||
{
|
||||
<strong class="text-sm">Purchased by:</strong>
|
||||
<strong class="text-sm">@L["RegistryPublic.PurchasedBy"]</strong>
|
||||
<div class="contributor-list">
|
||||
@foreach (var purchaser in item.Purchasers)
|
||||
{
|
||||
@@ -119,18 +119,19 @@ else
|
||||
}
|
||||
else if (item.CanViewPurchasers && item.CurrentUserPurchasedQuantity > 0)
|
||||
{
|
||||
<strong class="text-sm">Purchased by:</strong>
|
||||
<strong class="text-sm">@L["RegistryPublic.PurchasedBy"]</strong>
|
||||
<div class="contributor-list">
|
||||
<span class="contributor-badge">You (@item.CurrentUserPurchasedQuantity)</span>
|
||||
<span class="contributor-badge">@L["RegistryPublic.YouQuantity", item.CurrentUserPurchasedQuantity]</span>
|
||||
@if (item.Purchasers.Count > 1)
|
||||
{
|
||||
<span class="contributor-badge">and @(item.Purchasers.Count - 1) other @(item.Purchasers.Count - 1 == 1 ? "person" : "people")</span>
|
||||
var otherCount = item.Purchasers.Count - 1;
|
||||
<span class="contributor-badge">@L["RegistryPublic.AndOtherPeople", otherCount, otherCount == 1 ? L["RegistryPublic.Person"].Value : L["RegistryPublic.People"].Value]</span>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="text-muted small">Purchased</span>
|
||||
<span class="text-muted small">@L["RegistryPublic.Purchased"]</span>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@@ -139,7 +140,7 @@ else
|
||||
<div class="mb-2">
|
||||
@if (Registry.IsAdmin)
|
||||
{
|
||||
<strong class="text-sm">Contributed by:</strong>
|
||||
<strong class="text-sm">@L["RegistryPublic.ContributedBy"]</strong>
|
||||
<div class="contributor-list">
|
||||
@foreach (var contributor in item.Contributors)
|
||||
{
|
||||
@@ -152,23 +153,23 @@ else
|
||||
var currentUserContribution = item.Contributors.FirstOrDefault(x => x.UserId == Registry.CurrentUserId);
|
||||
if (currentUserContribution is not null)
|
||||
{
|
||||
<strong class="text-sm">Contributed by:</strong>
|
||||
<strong class="text-sm">@L["RegistryPublic.ContributedBy"]</strong>
|
||||
<div class="contributor-list">
|
||||
<span class="contributor-badge">@currentUserContribution.DisplayName (@currentUserContribution.Amount.ToString("0.00") @item.CurrencyCode)</span>
|
||||
@if (item.Contributors.Count > 1)
|
||||
{
|
||||
<span class="contributor-badge">and others</span>
|
||||
<span class="contributor-badge">@L["RegistryPublic.AndOthers"]</span>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="text-muted small">Contributed</span>
|
||||
<span class="text-muted small">@L["RegistryPublic.Contributed"]</span>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="text-muted small">Contributed</span>
|
||||
<span class="text-muted small">@L["RegistryPublic.Contributed"]</span>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@@ -177,24 +178,24 @@ else
|
||||
|
||||
@if (!IsAuthenticated)
|
||||
{
|
||||
<button class="btn btn-outline-primary btn-sm" @onclick="() => LoginRedirect()">Login to purchase</button>
|
||||
<button class="btn btn-outline-primary btn-sm" @onclick="() => LoginRedirect()">@L["RegistryPublic.LoginToPurchase"]</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="d-flex gap-2 flex-wrap">
|
||||
@if (!string.IsNullOrWhiteSpace(item.ProductUrl) && item.CurrentUserPurchasedQuantity == 0)
|
||||
{
|
||||
<button class="btn btn-primary btn-sm" @onclick="() => OpenPurchasePrompt(item.Id, openTab: true)">Purchase</button>
|
||||
<button class="btn btn-primary btn-sm" @onclick="() => OpenPurchasePrompt(item.Id, openTab: true)">@L["RegistryPublic.Purchase"]</button>
|
||||
}
|
||||
|
||||
<button class="btn btn-success btn-sm" @onclick="() => OpenPurchaseManagementPromptAsync(item.Id)">
|
||||
@(Registry.IsAdmin ? "Manage purchases" : (item.CurrentUserPurchasedQuantity > 0 ? "Edit purchase" : "Mark purchased"))
|
||||
@(Registry.IsAdmin ? L["RegistryPublic.ManagePurchases"].Value : (item.CurrentUserPurchasedQuantity > 0 ? L["RegistryPublic.EditPurchase"].Value : L["RegistryPublic.MarkPurchased"].Value))
|
||||
</button>
|
||||
|
||||
@if (item.ParticipationAllowed)
|
||||
{
|
||||
<button class="btn btn-secondary btn-sm" @onclick="() => OpenContributionActionAsync(item.Id, item.CurrentUserContributionAmount)">
|
||||
@(Registry.IsAdmin ? "Manage participations" : (item.CurrentUserContributionAmount > 0 ? "Edit participation" : "Partially fulfill"))
|
||||
@(Registry.IsAdmin ? L["RegistryPublic.ManageParticipations"].Value : (item.CurrentUserContributionAmount > 0 ? L["RegistryPublic.EditParticipation"].Value : L["RegistryPublic.PartiallyFulfill"].Value))
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
@@ -214,36 +215,20 @@ else
|
||||
{
|
||||
<div class="prompt-overlay">
|
||||
<div class="prompt-card">
|
||||
<h3>Mark as purchased</h3>
|
||||
<p>How many units did you purchase?</p>
|
||||
<h3>@L["RegistryPublic.MarkAsPurchased"]</h3>
|
||||
<p>@L["RegistryPublic.HowManyUnitsPurchased"]</p>
|
||||
<InputNumber class="form-control" @bind-Value="PurchasedQuantity" />
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(PurchaseItemUrl))
|
||||
{
|
||||
<div class="mt-3">
|
||||
<a href="@PurchaseItemUrl" target="_blank" class="btn btn-outline-primary btn-sm w-100">Open product link</a>
|
||||
<a href="@PurchaseItemUrl" target="_blank" class="btn btn-outline-primary btn-sm w-100">@L["RegistryPublic.OpenProductLink"]</a>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="d-flex gap-2 mt-3">
|
||||
<button class="btn btn-success" @onclick="ConfirmPurchaseAsync">Confirm purchase</button>
|
||||
<button class="btn btn-outline-secondary" @onclick="ClosePurchasePrompt">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (ShowContributionPrompt)
|
||||
{
|
||||
<div class="prompt-overlay">
|
||||
<div class="prompt-card">
|
||||
<h3>Log contribution</h3>
|
||||
<p>Transferred amount</p>
|
||||
<InputNumber class="form-control" @bind-Value="ContributionAmount" />
|
||||
<p class="mt-2">Message: @ContributionMessage</p>
|
||||
<div class="d-flex gap-2 mt-3">
|
||||
<button class="btn btn-success" @onclick="ConfirmContributionAsync">Confirm</button>
|
||||
<button class="btn btn-outline-secondary" @onclick="CloseContributionPrompt">Cancel</button>
|
||||
<button class="btn btn-success" @onclick="ConfirmPurchaseAsync">@L["RegistryPublic.ConfirmPurchase"]</button>
|
||||
<button class="btn btn-outline-secondary" @onclick="ClosePurchasePrompt">@L["Common.Cancel"]</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -253,20 +238,20 @@ else
|
||||
{
|
||||
<div class="prompt-overlay">
|
||||
<div class="prompt-card">
|
||||
<h3>Select purchaser to unmark</h3>
|
||||
<p>Multiple users have purchased this item. Choose which purchase to unmark:</p>
|
||||
<h3>@L["RegistryPublic.SelectPurchaserToUnmark"]</h3>
|
||||
<p>@L["RegistryPublic.MultipleUsersPurchased"]</p>
|
||||
<div class="purchaser-list">
|
||||
@foreach (var purchaser in PurchasersToUnmark)
|
||||
{
|
||||
<div class="purchaser-item">
|
||||
<span>@purchaser.DisplayName (@purchaser.Quantity)</span>
|
||||
<button class="btn btn-warning btn-sm" @onclick="() => UnmarkPurchaserAsync(purchaser.UserId)">Unmark</button>
|
||||
<button class="btn btn-warning btn-sm" @onclick="() => UnmarkPurchaserAsync(purchaser.UserId)">@L["RegistryPublic.Unmark"]</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="d-flex gap-2 mt-3">
|
||||
<button class="btn btn-danger" @onclick="UnmarkAllPurchasersAsync">Unmark all</button>
|
||||
<button class="btn btn-outline-secondary" @onclick="ClosePurchaserSelectionPrompt">Cancel</button>
|
||||
<button class="btn btn-danger" @onclick="UnmarkAllPurchasersAsync">@L["RegistryPublic.UnmarkAll"]</button>
|
||||
<button class="btn btn-outline-secondary" @onclick="ClosePurchaserSelectionPrompt">@L["Common.Cancel"]</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -276,29 +261,29 @@ else
|
||||
{
|
||||
<div class="prompt-overlay">
|
||||
<div class="prompt-card">
|
||||
<h3>Partially fulfill item</h3>
|
||||
<h3>@L["RegistryPublic.PartiallyFulfillItem"]</h3>
|
||||
|
||||
@if (PartialFulfillStep == 1)
|
||||
{
|
||||
<p>Select how you want to donate:</p>
|
||||
<p>@L["RegistryPublic.SelectDonateMethod"]</p>
|
||||
|
||||
<div class="d-flex flex-column gap-2">
|
||||
@if (HasIbanPaymentOption())
|
||||
{
|
||||
<button class="btn btn-outline-primary text-start" @onclick="() => SelectPaymentMethod(ContributionPaymentMethodType.Iban)">
|
||||
IBAN transfer
|
||||
@L["RegistryPublic.IbanTransfer"]
|
||||
</button>
|
||||
}
|
||||
@if (HasSingleQrPaymentOption())
|
||||
{
|
||||
<button class="btn btn-outline-primary text-start" @onclick="() => SelectPaymentMethod(ContributionPaymentMethodType.SingleQrCode)">
|
||||
Single QR code
|
||||
@L["RegistryPublic.SingleQrCode"]
|
||||
</button>
|
||||
}
|
||||
@if (HasAmountSpecificQrPaymentOption())
|
||||
{
|
||||
<button class="btn btn-outline-primary text-start" @onclick="() => SelectPaymentMethod(ContributionPaymentMethodType.AmountSpecificQrCode)">
|
||||
QR code per amount
|
||||
@L["RegistryPublic.QrCodePerAmount"]
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
@@ -306,10 +291,10 @@ else
|
||||
@if (SelectedPaymentMethod == ContributionPaymentMethodType.Iban && !string.IsNullOrWhiteSpace(Registry?.BankAccountIban))
|
||||
{
|
||||
<div class="alert alert-secondary mt-3 mb-0">
|
||||
<strong>IBAN:</strong> @Registry.BankAccountIban
|
||||
<strong>@L["RegistryPublic.IBAN"]:</strong> @Registry.BankAccountIban
|
||||
@if (!string.IsNullOrWhiteSpace(Registry.BankAccountBic))
|
||||
{
|
||||
<span> | <strong>BIC:</strong> @Registry.BankAccountBic</span>
|
||||
<span> | <strong>@L["RegistryPublic.BIC"]:</strong> @Registry.BankAccountBic</span>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@@ -319,25 +304,25 @@ else
|
||||
<div class="mt-3 text-center">
|
||||
<img src="@BuildQrImageUrl(SelectedPaymentQrCodeUrl)" alt="Payment QR code" class="img-fluid" style="max-height: 240px;" />
|
||||
<div class="mt-2">
|
||||
<a href="@SelectedPaymentQrCodeUrl" target="_blank" rel="noopener noreferrer">Open payment link</a>
|
||||
<a href="@SelectedPaymentQrCodeUrl" target="_blank" rel="noopener noreferrer">@L["RegistryPublic.OpenPaymentLink"]</a>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="d-flex gap-2 mt-3">
|
||||
<button class="btn btn-primary" @onclick="ContinueContributionWizard" disabled="@(SelectedPaymentMethod is null)">Next</button>
|
||||
<button class="btn btn-outline-secondary" @onclick="CloseContributionPrompt">Cancel</button>
|
||||
<button class="btn btn-primary" @onclick="ContinueContributionWizard" disabled="@(SelectedPaymentMethod is null)">@L["Common.Next"]</button>
|
||||
<button class="btn btn-outline-secondary" @onclick="CloseContributionPrompt">@L["Common.Cancel"]</button>
|
||||
</div>
|
||||
}
|
||||
else if (PartialFulfillStep == 2)
|
||||
{
|
||||
<p>How much did you add for this item?</p>
|
||||
<p>@L["RegistryPublic.HowMuchAdded"]</p>
|
||||
<InputNumber class="form-control" @bind-Value="ContributionAmount" @bind-Value:after="OnContributionAmountChanged" />
|
||||
<p class="mt-2">Message: @ContributionMessage</p>
|
||||
<p class="mt-2">@L["Common.Message"]: @ContributionMessage</p>
|
||||
|
||||
@if (SelectedPaymentMethod == ContributionPaymentMethodType.AmountSpecificQrCode && ContributionAmount > 0 && string.IsNullOrWhiteSpace(SelectedPaymentQrCodeUrl))
|
||||
{
|
||||
<p class="text-muted mt-2 mb-0">No QR code configured for this exact amount.</p>
|
||||
<p class="text-muted mt-2 mb-0">@L["RegistryPublic.NoQrForAmount"]</p>
|
||||
}
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(SelectedPaymentQrCodeUrl))
|
||||
@@ -345,15 +330,15 @@ else
|
||||
<div class="mt-3 text-center">
|
||||
<img src="@BuildQrImageUrl(SelectedPaymentQrCodeUrl)" alt="Payment QR code" class="img-fluid" style="max-height: 240px;" />
|
||||
<div class="mt-2">
|
||||
<a href="@SelectedPaymentQrCodeUrl" target="_blank" rel="noopener noreferrer">Open payment link</a>
|
||||
<a href="@SelectedPaymentQrCodeUrl" target="_blank" rel="noopener noreferrer">@L["RegistryPublic.OpenPaymentLink"]</a>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="d-flex gap-2 mt-3">
|
||||
<button class="btn btn-success" @onclick="ConfirmContributionAsync">Confirm</button>
|
||||
<button class="btn btn-outline-secondary" @onclick="BackContributionWizard">Back</button>
|
||||
<button class="btn btn-outline-secondary" @onclick="CloseContributionPrompt">Cancel</button>
|
||||
<button class="btn btn-success" @onclick="ConfirmContributionAsync">@L["RegistryPublic.Confirm"]</button>
|
||||
<button class="btn btn-outline-secondary" @onclick="BackContributionWizard">@L["Common.Back"]</button>
|
||||
<button class="btn btn-outline-secondary" @onclick="CloseContributionPrompt">@L["Common.Cancel"]</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@@ -364,14 +349,14 @@ else
|
||||
{
|
||||
<div class="prompt-overlay">
|
||||
<div class="prompt-card">
|
||||
<h3>Manage purchase</h3>
|
||||
<h3>@L["RegistryPublic.ManagePurchase"]</h3>
|
||||
|
||||
@if (Registry?.IsAdmin == true)
|
||||
{
|
||||
<label class="form-label">User</label>
|
||||
<InputText class="form-control mb-2" @bind-Value="UserFilterText" placeholder="Search user" />
|
||||
<label class="form-label">@L["RegistryPublic.User"]</label>
|
||||
<InputText class="form-control mb-2" @bind-Value="UserFilterText" placeholder="@L["Common.SearchUser"]" />
|
||||
<select class="form-select mb-3" value="@SelectedPurchaseUserId" @onchange="OnPurchaseManagedUserChanged">
|
||||
<option value="">Select user</option>
|
||||
<option value="">@L["Common.SelectUser"]</option>
|
||||
@foreach (var user in FilteredSelectableUsers)
|
||||
{
|
||||
<option value="@user.UserId">@user.DisplayName</option>
|
||||
@@ -379,13 +364,13 @@ else
|
||||
</select>
|
||||
}
|
||||
|
||||
<label class="form-label">Quantity</label>
|
||||
<label class="form-label">@L["Common.Quantity"]</label>
|
||||
<InputNumber class="form-control" @bind-Value="ManagedPurchaseQuantity" />
|
||||
|
||||
<div class="d-flex gap-2 mt-3">
|
||||
<button class="btn btn-success" @onclick="SavePurchaseManagementAsync">Save</button>
|
||||
<button class="btn btn-outline-danger" @onclick="RemoveManagedPurchaseAsync">Remove</button>
|
||||
<button class="btn btn-outline-secondary" @onclick="ClosePurchaseManagementPrompt">Cancel</button>
|
||||
<button class="btn btn-success" @onclick="SavePurchaseManagementAsync">@L["Common.Save"]</button>
|
||||
<button class="btn btn-outline-danger" @onclick="RemoveManagedPurchaseAsync">@L["Common.Remove"]</button>
|
||||
<button class="btn btn-outline-secondary" @onclick="ClosePurchaseManagementPrompt">@L["Common.Cancel"]</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -395,14 +380,14 @@ else
|
||||
{
|
||||
<div class="prompt-overlay">
|
||||
<div class="prompt-card">
|
||||
<h3>Manage participation</h3>
|
||||
<h3>@L["RegistryPublic.ManageParticipation"]</h3>
|
||||
|
||||
@if (Registry?.IsAdmin == true)
|
||||
{
|
||||
<label class="form-label">User</label>
|
||||
<InputText class="form-control mb-2" @bind-Value="UserFilterText" placeholder="Search user" />
|
||||
<label class="form-label">@L["RegistryPublic.User"]</label>
|
||||
<InputText class="form-control mb-2" @bind-Value="UserFilterText" placeholder="@L["Common.SearchUser"]" />
|
||||
<select class="form-select mb-3" value="@SelectedContributionUserId" @onchange="OnContributionManagedUserChanged">
|
||||
<option value="">Select user</option>
|
||||
<option value="">@L["Common.SelectUser"]</option>
|
||||
@foreach (var user in FilteredSelectableUsers)
|
||||
{
|
||||
<option value="@user.UserId">@user.DisplayName</option>
|
||||
@@ -410,16 +395,16 @@ else
|
||||
</select>
|
||||
}
|
||||
|
||||
<label class="form-label">Amount</label>
|
||||
<label class="form-label">@L["Common.Amount"]</label>
|
||||
<InputNumber class="form-control" @bind-Value="ManagedContributionAmount" />
|
||||
|
||||
<label class="form-label mt-2">Message</label>
|
||||
<label class="form-label mt-2">@L["Common.Message"]</label>
|
||||
<InputText class="form-control" @bind-Value="ManagedContributionMessage" />
|
||||
|
||||
<div class="d-flex gap-2 mt-3">
|
||||
<button class="btn btn-success" @onclick="SaveContributionManagementAsync">Save</button>
|
||||
<button class="btn btn-outline-danger" @onclick="RemoveManagedContributionAsync">Remove</button>
|
||||
<button class="btn btn-outline-secondary" @onclick="CloseContributionManagementPrompt">Cancel</button>
|
||||
<button class="btn btn-success" @onclick="SaveContributionManagementAsync">@L["Common.Save"]</button>
|
||||
<button class="btn btn-outline-danger" @onclick="RemoveManagedContributionAsync">@L["Common.Remove"]</button>
|
||||
<button class="btn btn-outline-secondary" @onclick="CloseContributionManagementPrompt">@L["Common.Cancel"]</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user