diff --git a/src/BirthList.Domain/Entities/Registry.cs b/src/BirthList.Domain/Entities/Registry.cs index ccadf7b..517e0ad 100644 --- a/src/BirthList.Domain/Entities/Registry.cs +++ b/src/BirthList.Domain/Entities/Registry.cs @@ -9,6 +9,7 @@ public class Registry public string? BabyName { get; set; } public byte[]? HeroImageData { get; set; } public string? HeroImageContentType { get; set; } + public string? HeroImagePath { get; set; } public string? HeaderContentHtml { get; set; } public string? ShippingAddress { get; set; } public string CurrencyCode { get; set; } = "EUR"; diff --git a/src/BirthList.Infrastructure/Migrations/20260725170215_AddHeroImagePathToRegistry.Designer.cs b/src/BirthList.Infrastructure/Migrations/20260725170215_AddHeroImagePathToRegistry.Designer.cs new file mode 100644 index 0000000..dc243e6 --- /dev/null +++ b/src/BirthList.Infrastructure/Migrations/20260725170215_AddHeroImagePathToRegistry.Designer.cs @@ -0,0 +1,564 @@ +// +using System; +using BirthList.Infrastructure.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace BirthList.Infrastructure.Migrations +{ + [DbContext(typeof(RegistryDbContext))] + [Migration("20260725170215_AddHeroImagePathToRegistry")] + partial class AddHeroImagePathToRegistry + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.26") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("BirthList.Domain.Entities.ItemContribution", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("ContributedAtUtc") + .HasColumnType("datetimeoffset"); + + b.Property("CurrencyCode") + .IsRequired() + .HasMaxLength(3) + .HasColumnType("nvarchar(3)"); + + b.Property("RegistryItemId") + .HasColumnType("uniqueidentifier"); + + b.Property("TransferMessage") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(450) + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RegistryItemId"); + + b.ToTable("ItemContributions"); + }); + + modelBuilder.Entity("BirthList.Domain.Entities.ItemPurchase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("PurchasedAtUtc") + .HasColumnType("datetimeoffset"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("RegistryItemId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(450) + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RegistryItemId"); + + b.ToTable("ItemPurchases"); + }); + + modelBuilder.Entity("BirthList.Domain.Entities.PlatformOwner", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AssignedAtUtc") + .HasColumnType("datetimeoffset"); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(450) + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("PlatformOwners"); + }); + + modelBuilder.Entity("BirthList.Domain.Entities.Registry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BabyName") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("BirthDate") + .HasColumnType("date"); + + b.Property("CreatedAtUtc") + .HasColumnType("datetimeoffset"); + + b.Property("CurrencyCode") + .IsRequired() + .HasMaxLength(3) + .HasColumnType("nvarchar(3)"); + + b.Property("HeaderContentHtml") + .HasColumnType("nvarchar(max)"); + + b.Property("HeroImageContentType") + .HasColumnType("nvarchar(max)"); + + b.Property("HeroImageData") + .HasColumnType("varbinary(max)"); + + b.Property("HeroImagePath") + .HasColumnType("nvarchar(max)"); + + b.Property("PublicLinkCode") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("RegistryType") + .HasColumnType("int"); + + b.Property("ShippingAddress") + .HasColumnType("nvarchar(max)"); + + b.Property("ThemeKey") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("nvarchar(250)"); + + b.HasKey("Id"); + + b.HasIndex("PublicLinkCode") + .IsUnique(); + + b.ToTable("Registries"); + }); + + modelBuilder.Entity("BirthList.Domain.Entities.RegistryAdmin", b => + { + b.Property("RegistryId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserId") + .HasMaxLength(450) + .HasColumnType("nvarchar(450)"); + + b.Property("AddedAtUtc") + .HasColumnType("datetimeoffset"); + + b.HasKey("RegistryId", "UserId"); + + b.ToTable("RegistryAdmins"); + }); + + modelBuilder.Entity("BirthList.Domain.Entities.RegistryAdminInvite", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ExpiresAtUtc") + .HasColumnType("datetimeoffset"); + + b.Property("RedeemedAtUtc") + .HasColumnType("datetimeoffset"); + + b.Property("RegistryId") + .HasColumnType("uniqueidentifier"); + + b.Property("SentToEmail") + .HasMaxLength(320) + .HasColumnType("nvarchar(320)"); + + b.Property("Token") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("nvarchar(120)"); + + b.HasKey("Id"); + + b.HasIndex("RegistryId"); + + b.HasIndex("Token") + .IsUnique(); + + b.ToTable("RegistryAdminInvites"); + }); + + modelBuilder.Entity("BirthList.Domain.Entities.RegistryItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CategoryId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAtUtc") + .HasColumnType("datetimeoffset"); + + b.Property("CurrencyCode") + .IsRequired() + .HasMaxLength(3) + .HasColumnType("nvarchar(3)"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("DesiredQuantity") + .HasColumnType("int"); + + b.Property("IsGiven") + .HasColumnType("bit"); + + b.Property("MoneyFulfilledAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("ParticipationAllowed") + .HasColumnType("bit"); + + b.Property("ParticipationTargetAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("PictureUrl") + .HasMaxLength(2048) + .HasColumnType("nvarchar(2048)"); + + b.Property("PreferSecondHand") + .HasColumnType("bit"); + + b.Property("PriceAmount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("ProductUrl") + .HasMaxLength(2048) + .HasColumnType("nvarchar(2048)"); + + b.Property("PurchasedQuantity") + .HasColumnType("int"); + + b.Property("RegistryId") + .HasColumnType("uniqueidentifier"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("RegistryId"); + + b.HasIndex("CategoryId", "SortOrder"); + + b.ToTable("RegistryItems"); + }); + + modelBuilder.Entity("BirthList.Domain.Entities.RegistryItemCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAtUtc") + .HasColumnType("datetimeoffset"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("RegistryId") + .HasColumnType("uniqueidentifier"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("RegistryId", "Name"); + + b.HasIndex("RegistryId", "SortOrder"); + + b.ToTable("RegistryItemCategories"); + }); + + modelBuilder.Entity("BirthList.Domain.Entities.RegistrySettings", b => + { + b.Property("RegistryId") + .HasColumnType("uniqueidentifier"); + + b.Property("BankAccountBic") + .HasMaxLength(11) + .HasColumnType("nvarchar(11)"); + + b.Property("BankAccountDisplayName") + .HasMaxLength(120) + .HasColumnType("nvarchar(120)"); + + b.Property("BankAccountIban") + .HasMaxLength(34) + .HasColumnType("nvarchar(34)"); + + b.Property("ContributionAmountQrCodesJson") + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("ContributionQrCodeUrl") + .HasMaxLength(2048) + .HasColumnType("nvarchar(2048)"); + + b.Property("HideHeaderName") + .HasColumnType("bit"); + + b.Property("ShowBankAccountName") + .HasColumnType("bit"); + + b.HasKey("RegistryId"); + + b.ToTable("RegistrySettings"); + }); + + modelBuilder.Entity("BirthList.Domain.Entities.RegistryVisit", b => + { + b.Property("RegistryId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserId") + .HasMaxLength(450) + .HasColumnType("nvarchar(450)"); + + b.Property("LastVisitedAtUtc") + .HasColumnType("datetimeoffset"); + + b.HasKey("RegistryId", "UserId"); + + b.ToTable("RegistryVisits"); + }); + + modelBuilder.Entity("BirthList.Domain.Entities.UserActionLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ActionType") + .HasColumnType("int"); + + b.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("CreatedAtUtc") + .HasColumnType("datetimeoffset"); + + b.Property("Details") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("RegistryId") + .HasColumnType("uniqueidentifier"); + + b.Property("RegistryItemId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(450) + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RegistryId"); + + b.ToTable("UserActionLogs"); + }); + + modelBuilder.Entity("BirthList.Domain.Entities.ItemContribution", b => + { + b.HasOne("BirthList.Domain.Entities.RegistryItem", "RegistryItem") + .WithMany("Contributions") + .HasForeignKey("RegistryItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegistryItem"); + }); + + modelBuilder.Entity("BirthList.Domain.Entities.ItemPurchase", b => + { + b.HasOne("BirthList.Domain.Entities.RegistryItem", "RegistryItem") + .WithMany("Purchases") + .HasForeignKey("RegistryItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RegistryItem"); + }); + + modelBuilder.Entity("BirthList.Domain.Entities.RegistryAdmin", b => + { + b.HasOne("BirthList.Domain.Entities.Registry", "Registry") + .WithMany("Admins") + .HasForeignKey("RegistryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Registry"); + }); + + modelBuilder.Entity("BirthList.Domain.Entities.RegistryAdminInvite", b => + { + b.HasOne("BirthList.Domain.Entities.Registry", "Registry") + .WithMany("AdminInvites") + .HasForeignKey("RegistryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Registry"); + }); + + modelBuilder.Entity("BirthList.Domain.Entities.RegistryItem", b => + { + b.HasOne("BirthList.Domain.Entities.RegistryItemCategory", "Category") + .WithMany("Items") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("BirthList.Domain.Entities.Registry", "Registry") + .WithMany("Items") + .HasForeignKey("RegistryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Category"); + + b.Navigation("Registry"); + }); + + modelBuilder.Entity("BirthList.Domain.Entities.RegistryItemCategory", b => + { + b.HasOne("BirthList.Domain.Entities.Registry", "Registry") + .WithMany("ItemCategories") + .HasForeignKey("RegistryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Registry"); + }); + + modelBuilder.Entity("BirthList.Domain.Entities.RegistrySettings", b => + { + b.HasOne("BirthList.Domain.Entities.Registry", "Registry") + .WithOne() + .HasForeignKey("BirthList.Domain.Entities.RegistrySettings", "RegistryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Registry"); + }); + + modelBuilder.Entity("BirthList.Domain.Entities.RegistryVisit", b => + { + b.HasOne("BirthList.Domain.Entities.Registry", "Registry") + .WithMany("Visits") + .HasForeignKey("RegistryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Registry"); + }); + + modelBuilder.Entity("BirthList.Domain.Entities.UserActionLog", b => + { + b.HasOne("BirthList.Domain.Entities.Registry", "Registry") + .WithMany("ActionLogs") + .HasForeignKey("RegistryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Registry"); + }); + + modelBuilder.Entity("BirthList.Domain.Entities.Registry", b => + { + b.Navigation("ActionLogs"); + + b.Navigation("AdminInvites"); + + b.Navigation("Admins"); + + b.Navigation("ItemCategories"); + + b.Navigation("Items"); + + b.Navigation("Visits"); + }); + + modelBuilder.Entity("BirthList.Domain.Entities.RegistryItem", b => + { + b.Navigation("Contributions"); + + b.Navigation("Purchases"); + }); + + modelBuilder.Entity("BirthList.Domain.Entities.RegistryItemCategory", b => + { + b.Navigation("Items"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/BirthList.Infrastructure/Migrations/20260725170215_AddHeroImagePathToRegistry.cs b/src/BirthList.Infrastructure/Migrations/20260725170215_AddHeroImagePathToRegistry.cs new file mode 100644 index 0000000..2fb89aa --- /dev/null +++ b/src/BirthList.Infrastructure/Migrations/20260725170215_AddHeroImagePathToRegistry.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace BirthList.Infrastructure.Migrations +{ + /// + public partial class AddHeroImagePathToRegistry : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "HeroImagePath", + table: "Registries", + type: "nvarchar(max)", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "HeroImagePath", + table: "Registries"); + } + } +} diff --git a/src/BirthList.Infrastructure/Migrations/RegistryDbContextModelSnapshot.cs b/src/BirthList.Infrastructure/Migrations/RegistryDbContextModelSnapshot.cs index 7a8ea6e..2544515 100644 --- a/src/BirthList.Infrastructure/Migrations/RegistryDbContextModelSnapshot.cs +++ b/src/BirthList.Infrastructure/Migrations/RegistryDbContextModelSnapshot.cs @@ -141,6 +141,9 @@ namespace BirthList.Infrastructure.Migrations b.Property("HeroImageData") .HasColumnType("varbinary(max)"); + b.Property("HeroImagePath") + .HasColumnType("nvarchar(max)"); + b.Property("PublicLinkCode") .IsRequired() .HasMaxLength(100) diff --git a/src/BirthList.Web/Components/Pages/RegistryAdmin.razor b/src/BirthList.Web/Components/Pages/RegistryAdmin.razor index 1e3bc02..4991118 100644 --- a/src/BirthList.Web/Components/Pages/RegistryAdmin.razor +++ b/src/BirthList.Web/Components/Pages/RegistryAdmin.razor @@ -349,12 +349,12 @@ else
- @if (!string.IsNullOrWhiteSpace(SettingsModel.HeroImageBase64)) + @if (!string.IsNullOrWhiteSpace(SettingsModel.HeroImagePath) || !string.IsNullOrWhiteSpace(SettingsModel.HeroImageBase64)) {
- Hero + Hero
diff --git a/src/BirthList.Web/Components/Pages/RegistryAdmin.razor.cs b/src/BirthList.Web/Components/Pages/RegistryAdmin.razor.cs index e523c8e..9089269 100644 --- a/src/BirthList.Web/Components/Pages/RegistryAdmin.razor.cs +++ b/src/BirthList.Web/Components/Pages/RegistryAdmin.razor.cs @@ -5,6 +5,7 @@ using BirthList.Web.Services; using Blazored.TextEditor; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Components.Forms; using Microsoft.AspNetCore.Components.Web; using Microsoft.JSInterop; @@ -23,6 +24,7 @@ public partial class RegistryAdmin : ComponentBase [Inject] private NavigationManager NavigationManager { get; set; } = null!; [Inject] private SmtpEmailSender EmailSender { get; set; } = null!; [Inject] private SmtpConfigurationStatusService SmtpConfigurationStatusService { get; set; } = null!; + [Inject] private IWebHostEnvironment WebHostEnvironment { get; set; } = null!; [Inject] private IJSRuntime JSRuntime { get; set; } = null!; protected RegistrySettingsEditModel SettingsModel { get; } = new(); @@ -817,12 +819,16 @@ public partial class RegistryAdmin : ComponentBase try { - const long maxFileSize = 10 * 1024 * 1024; // 10 MB + const long maxOriginalFileSize = 10 * 1024 * 1024; // 10 MB + const long maxProcessedFileSize = 3 * 1024 * 1024; // 3 MB + const int maxHeroImageWidth = 1920; + const int maxHeroImageHeight = 1080; + var file = e.File; - if (file.Size > maxFileSize) + if (file.Size > maxOriginalFileSize) { - HeroImageUploadMessage = $"File size exceeds maximum of 10 MB"; + HeroImageUploadMessage = "File size exceeds maximum of 10 MB"; return; } @@ -832,11 +838,46 @@ public partial class RegistryAdmin : ComponentBase return; } - using var memoryStream = new MemoryStream(); - await file.OpenReadStream(maxFileSize).CopyToAsync(memoryStream).ConfigureAwait(false); + var targetContentType = file.ContentType.Equals("image/png", StringComparison.OrdinalIgnoreCase) + ? "image/png" + : "image/jpeg"; - SettingsModel.HeroImageBase64 = Convert.ToBase64String(memoryStream.ToArray()); - SettingsModel.HeroImageContentType = file.ContentType; + var processedFile = file.ContentType.Equals("image/svg+xml", StringComparison.OrdinalIgnoreCase) + ? file + : await file.RequestImageFileAsync(targetContentType, maxHeroImageWidth, maxHeroImageHeight).ConfigureAwait(false); + + if (processedFile.Size > maxProcessedFileSize) + { + HeroImageUploadMessage = "Image is still too large after optimization. Please choose a smaller image."; + return; + } + + var webRootPath = WebHostEnvironment.WebRootPath; + if (string.IsNullOrWhiteSpace(webRootPath)) + { + HeroImageUploadMessage = "Unable to resolve web root path for image storage."; + return; + } + + var uploadsFolder = Path.Combine(webRootPath, "uploads", "registry-hero"); + Directory.CreateDirectory(uploadsFolder); + + var extension = GetHeroImageExtension(processedFile.ContentType); + var fileName = $"{RegistryId:N}-{Guid.NewGuid():N}{extension}"; + var relativePath = $"/uploads/registry-hero/{fileName}"; + var fullPath = Path.Combine(uploadsFolder, fileName); + + await using (var destinationStream = new FileStream(fullPath, FileMode.Create, FileAccess.Write, FileShare.None)) + await using (var sourceStream = processedFile.OpenReadStream(maxProcessedFileSize)) + { + await sourceStream.CopyToAsync(destinationStream).ConfigureAwait(false); + } + + DeleteManagedHeroImage(SettingsModel.HeroImagePath); + + SettingsModel.HeroImagePath = relativePath; + SettingsModel.HeroImageBase64 = null; + SettingsModel.HeroImageContentType = null; HeroImageUploadMessage = "Image uploaded successfully. Click 'Save Settings' to apply."; } @@ -848,10 +889,41 @@ public partial class RegistryAdmin : ComponentBase protected Task RemoveHeroImageAsync() { + DeleteManagedHeroImage(SettingsModel.HeroImagePath); + SettingsModel.HeroImagePath = string.Empty; SettingsModel.HeroImageBase64 = string.Empty; SettingsModel.HeroImageContentType = null; HeroImageUploadMessage = "Image will be removed when you save settings."; return Task.CompletedTask; } + + private static string GetHeroImageExtension(string contentType) + { + return contentType.ToLowerInvariant() switch + { + "image/png" => ".png", + "image/webp" => ".webp", + "image/svg+xml" => ".svg", + _ => ".jpg" + }; + } + + private void DeleteManagedHeroImage(string? relativePath) + { + if (string.IsNullOrWhiteSpace(relativePath) + || !relativePath.StartsWith("/uploads/registry-hero/", StringComparison.OrdinalIgnoreCase) + || string.IsNullOrWhiteSpace(WebHostEnvironment.WebRootPath)) + { + return; + } + + var sanitizedPath = relativePath.TrimStart('/').Replace('/', Path.DirectorySeparatorChar); + var fullPath = Path.Combine(WebHostEnvironment.WebRootPath, sanitizedPath); + + if (File.Exists(fullPath)) + { + File.Delete(fullPath); + } + } } diff --git a/src/BirthList.Web/Components/Pages/RegistryPublic.razor b/src/BirthList.Web/Components/Pages/RegistryPublic.razor index 74600ad..f93d848 100644 --- a/src/BirthList.Web/Components/Pages/RegistryPublic.razor +++ b/src/BirthList.Web/Components/Pages/RegistryPublic.razor @@ -12,10 +12,10 @@ else {
- @if (!string.IsNullOrWhiteSpace(Registry.HeroImageBase64)) + @if (!string.IsNullOrWhiteSpace(Registry.HeroImagePath) || !string.IsNullOrWhiteSpace(Registry.HeroImageBase64)) {
- @Registry.Title + @Registry.Title
} diff --git a/src/BirthList.Web/Features/Registries/RegistryModels.cs b/src/BirthList.Web/Features/Registries/RegistryModels.cs index f47605b..a36c7f5 100644 --- a/src/BirthList.Web/Features/Registries/RegistryModels.cs +++ b/src/BirthList.Web/Features/Registries/RegistryModels.cs @@ -55,6 +55,7 @@ public sealed class RegistrySettingsEditModel public DateOnly? BirthDate { get; set; } public string? HeroImageBase64 { get; set; } public string? HeroImageContentType { get; set; } + public string? HeroImagePath { get; set; } public string? HeaderContentHtml { get; set; } public string? ShippingAddress { get; set; } public string CurrencyCode { get; set; } = "€"; @@ -86,6 +87,7 @@ public sealed class RegistryPublicViewModel public string? BabyName { get; init; } public string? HeroImageBase64 { get; init; } public string? HeroImageContentType { get; init; } + public string? HeroImagePath { get; init; } public string? HeaderContentHtml { get; init; } public string? ShippingAddress { get; init; } public string CurrencyCode { get; init; } = "€"; diff --git a/src/BirthList.Web/Features/Registries/RegistryService.cs b/src/BirthList.Web/Features/Registries/RegistryService.cs index 0826dd3..0e7269a 100644 --- a/src/BirthList.Web/Features/Registries/RegistryService.cs +++ b/src/BirthList.Web/Features/Registries/RegistryService.cs @@ -301,6 +301,7 @@ internal sealed class RegistryService(RegistryDbContext registryDbContext, Appli BabyName = registry.BabyName, HeroImageBase64 = registry.HeroImageData != null ? Convert.ToBase64String(registry.HeroImageData) : null, HeroImageContentType = registry.HeroImageContentType, + HeroImagePath = registry.HeroImagePath, HeaderContentHtml = registry.HeaderContentHtml, ShippingAddress = registry.ShippingAddress, CurrencyCode = registry.CurrencyCode, @@ -341,6 +342,7 @@ internal sealed class RegistryService(RegistryDbContext registryDbContext, Appli BirthDate = registry.BirthDate, HeroImageBase64 = registry.HeroImageData != null ? Convert.ToBase64String(registry.HeroImageData) : null, HeroImageContentType = registry.HeroImageContentType, + HeroImagePath = registry.HeroImagePath, HeaderContentHtml = registry.HeaderContentHtml, ShippingAddress = registry.ShippingAddress, CurrencyCode = registry.CurrencyCode, @@ -380,15 +382,24 @@ internal sealed class RegistryService(RegistryDbContext registryDbContext, Appli registry.BabyName = string.IsNullOrWhiteSpace(model.BabyName) ? null : model.BabyName.Trim(); registry.BirthDate = model.BirthDate; - if (!string.IsNullOrWhiteSpace(model.HeroImageBase64)) + if (!string.IsNullOrWhiteSpace(model.HeroImagePath)) + { + registry.HeroImagePath = model.HeroImagePath.Trim(); + registry.HeroImageData = null; + registry.HeroImageContentType = null; + } + else if (!string.IsNullOrWhiteSpace(model.HeroImageBase64)) { registry.HeroImageData = Convert.FromBase64String(model.HeroImageBase64); registry.HeroImageContentType = model.HeroImageContentType; + registry.HeroImagePath = null; } - else if (string.IsNullOrWhiteSpace(model.HeroImageBase64) && model.HeroImageBase64 == string.Empty) + else if ((string.IsNullOrWhiteSpace(model.HeroImageBase64) && model.HeroImageBase64 == string.Empty) + || (string.IsNullOrWhiteSpace(model.HeroImagePath) && model.HeroImagePath == string.Empty)) { registry.HeroImageData = null; registry.HeroImageContentType = null; + registry.HeroImagePath = null; } registry.HeaderContentHtml = string.IsNullOrWhiteSpace(model.HeaderContentHtml) ? null : model.HeaderContentHtml;