Add hero image support for registries

- Registry entity and DB updated with hero image fields
- Admin UI: upload, preview, remove hero image with validation
- Public registry page displays hero image if present
- View models and RegistryService handle hero image data (base64)
- Localization updated for hero image UI strings
- Minor: Fix ShowCreateForm property in Home.razor
This commit is contained in:
Arne Moerman
2026-07-25 18:07:25 +02:00
parent f1186b31f9
commit b323cdd47a
18 changed files with 1441 additions and 1 deletions
@@ -7,6 +7,8 @@ public class Registry
public string Title { get; set; } = string.Empty;
public DateOnly? BirthDate { get; set; }
public string? BabyName { get; set; }
public byte[]? HeroImageData { get; set; }
public string? HeroImageContentType { get; set; }
public string? HeaderContentHtml { get; set; }
public string? ShippingAddress { get; set; }
public string CurrencyCode { get; set; } = "EUR";
@@ -0,0 +1,424 @@
// <auto-generated />
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("20260517000000_InitialCreate")]
partial class InitialCreate
{
/// <inheritdoc />
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<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<decimal>("Amount")
.HasPrecision(18, 2)
.HasColumnType("decimal(18,2)");
b.Property<DateTimeOffset>("ContributedAtUtc")
.HasColumnType("datetimeoffset");
b.Property<string>("CurrencyCode")
.IsRequired()
.HasMaxLength(3)
.HasColumnType("nvarchar(3)");
b.Property<Guid>("RegistryItemId")
.HasColumnType("uniqueidentifier");
b.Property<string>("TransferMessage")
.IsRequired()
.HasMaxLength(500)
.HasColumnType("nvarchar(500)");
b.Property<string>("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<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<DateTimeOffset>("PurchasedAtUtc")
.HasColumnType("datetimeoffset");
b.Property<int>("Quantity")
.HasColumnType("int");
b.Property<Guid>("RegistryItemId")
.HasColumnType("uniqueidentifier");
b.Property<string>("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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<DateTimeOffset>("AssignedAtUtc")
.HasColumnType("datetimeoffset");
b.Property<string>("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<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<string>("BabyName")
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<DateOnly?>("BirthDate")
.HasColumnType("date");
b.Property<DateTimeOffset>("CreatedAtUtc")
.HasColumnType("datetimeoffset");
b.Property<string>("CurrencyCode")
.IsRequired()
.HasMaxLength(3)
.HasColumnType("nvarchar(3)");
b.Property<string>("HeaderContentHtml")
.HasColumnType("nvarchar(max)");
b.Property<string>("PublicLinkCode")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<int>("RegistryType")
.HasColumnType("int");
b.Property<string>("ShippingAddress")
.HasColumnType("nvarchar(max)");
b.Property<string>("ThemeKey")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<string>("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<Guid>("RegistryId")
.HasColumnType("uniqueidentifier");
b.Property<string>("UserId")
.HasMaxLength(450)
.HasColumnType("nvarchar(450)");
b.Property<DateTimeOffset>("AddedAtUtc")
.HasColumnType("datetimeoffset");
b.HasKey("RegistryId", "UserId");
b.ToTable("RegistryAdmins");
});
modelBuilder.Entity("BirthList.Domain.Entities.RegistryAdminInvite", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<DateTimeOffset>("ExpiresAtUtc")
.HasColumnType("datetimeoffset");
b.Property<DateTimeOffset?>("RedeemedAtUtc")
.HasColumnType("datetimeoffset");
b.Property<Guid>("RegistryId")
.HasColumnType("uniqueidentifier");
b.Property<string>("SentToEmail")
.HasMaxLength(320)
.HasColumnType("nvarchar(320)");
b.Property<string>("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<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<bool>("CanBeSecondHand")
.HasColumnType("bit");
b.Property<DateTimeOffset>("CreatedAtUtc")
.HasColumnType("datetimeoffset");
b.Property<string>("CurrencyCode")
.IsRequired()
.HasMaxLength(3)
.HasColumnType("nvarchar(3)");
b.Property<string>("Description")
.HasColumnType("nvarchar(max)");
b.Property<int>("DesiredQuantity")
.HasColumnType("int");
b.Property<bool>("IsGiven")
.HasColumnType("bit");
b.Property<decimal>("MoneyFulfilledAmount")
.HasPrecision(18, 2)
.HasColumnType("decimal(18,2)");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(300)
.HasColumnType("nvarchar(300)");
b.Property<bool>("ParticipationAllowed")
.HasColumnType("bit");
b.Property<decimal?>("ParticipationTargetAmount")
.HasPrecision(18, 2)
.HasColumnType("decimal(18,2)");
b.Property<string>("PictureUrl")
.HasMaxLength(2048)
.HasColumnType("nvarchar(2048)");
b.Property<decimal?>("PriceAmount")
.HasPrecision(18, 2)
.HasColumnType("decimal(18,2)");
b.Property<string>("ProductUrl")
.HasMaxLength(2048)
.HasColumnType("nvarchar(2048)");
b.Property<int>("PurchasedQuantity")
.HasColumnType("int");
b.Property<Guid>("RegistryId")
.HasColumnType("uniqueidentifier");
b.HasKey("Id");
b.HasIndex("RegistryId");
b.ToTable("RegistryItems");
});
modelBuilder.Entity("BirthList.Domain.Entities.RegistrySettings", b =>
{
b.Property<Guid>("RegistryId")
.HasColumnType("uniqueidentifier");
b.Property<string>("BankAccountBic")
.HasMaxLength(11)
.HasColumnType("nvarchar(11)");
b.Property<string>("BankAccountDisplayName")
.HasMaxLength(120)
.HasColumnType("nvarchar(120)");
b.Property<string>("BankAccountIban")
.HasMaxLength(34)
.HasColumnType("nvarchar(34)");
b.HasKey("RegistryId");
b.ToTable("RegistrySettings");
});
modelBuilder.Entity("BirthList.Domain.Entities.RegistryVisit", b =>
{
b.Property<Guid>("RegistryId")
.HasColumnType("uniqueidentifier");
b.Property<string>("UserId")
.HasMaxLength(450)
.HasColumnType("nvarchar(450)");
b.Property<DateTimeOffset>("LastVisitedAtUtc")
.HasColumnType("datetimeoffset");
b.HasKey("RegistryId", "UserId");
b.ToTable("RegistryVisits");
});
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.Registry", "Registry")
.WithMany("Items")
.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.Registry", b =>
{
b.Navigation("AdminInvites");
b.Navigation("Admins");
b.Navigation("Items");
b.Navigation("Visits");
});
modelBuilder.Entity("BirthList.Domain.Entities.RegistryItem", b =>
{
b.Navigation("Contributions");
b.Navigation("Purchases");
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,275 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace BirthList.Infrastructure.Migrations
{
/// <inheritdoc />
public partial class InitialCreate : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "PlatformOwners",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
UserId = table.Column<string>(type: "nvarchar(450)", maxLength: 450, nullable: false),
AssignedAtUtc = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_PlatformOwners", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Registries",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Title = table.Column<string>(type: "nvarchar(250)", maxLength: 250, nullable: false),
PublicLinkCode = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
BabyName = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: true),
BirthDate = table.Column<DateOnly>(type: "date", nullable: true),
RegistryType = table.Column<int>(type: "int", nullable: false),
HeaderContentHtml = table.Column<string>(type: "nvarchar(max)", nullable: true),
ShippingAddress = table.Column<string>(type: "nvarchar(max)", nullable: true),
CurrencyCode = table.Column<string>(type: "nvarchar(3)", maxLength: 3, nullable: false),
ThemeKey = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
CreatedAtUtc = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Registries", x => x.Id);
});
migrationBuilder.CreateTable(
name: "RegistryAdmins",
columns: table => new
{
RegistryId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
UserId = table.Column<string>(type: "nvarchar(450)", maxLength: 450, nullable: false),
AddedAtUtc = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_RegistryAdmins", x => new { x.RegistryId, x.UserId });
table.ForeignKey(
name: "FK_RegistryAdmins_Registries_RegistryId",
column: x => x.RegistryId,
principalTable: "Registries",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "RegistryAdminInvites",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
RegistryId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Token = table.Column<string>(type: "nvarchar(120)", maxLength: 120, nullable: false),
SentToEmail = table.Column<string>(type: "nvarchar(320)", maxLength: 320, nullable: true),
ExpiresAtUtc = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: false),
RedeemedAtUtc = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_RegistryAdminInvites", x => x.Id);
table.ForeignKey(
name: "FK_RegistryAdminInvites_Registries_RegistryId",
column: x => x.RegistryId,
principalTable: "Registries",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "RegistryItems",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
RegistryId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Name = table.Column<string>(type: "nvarchar(300)", maxLength: 300, nullable: false),
Description = table.Column<string>(type: "nvarchar(max)", nullable: true),
PictureUrl = table.Column<string>(type: "nvarchar(2048)", maxLength: 2048, nullable: true),
ProductUrl = table.Column<string>(type: "nvarchar(2048)", maxLength: 2048, nullable: true),
CurrencyCode = table.Column<string>(type: "nvarchar(3)", maxLength: 3, nullable: false),
PriceAmount = table.Column<decimal>(type: "decimal(18,2)", precision: 18, scale: 2, nullable: true),
DesiredQuantity = table.Column<int>(type: "int", nullable: false),
PurchasedQuantity = table.Column<int>(type: "int", nullable: false),
ParticipationAllowed = table.Column<bool>(type: "bit", nullable: false),
ParticipationTargetAmount = table.Column<decimal>(type: "decimal(18,2)", precision: 18, scale: 2, nullable: true),
MoneyFulfilledAmount = table.Column<decimal>(type: "decimal(18,2)", precision: 18, scale: 2, nullable: false),
CanBeSecondHand = table.Column<bool>(type: "bit", nullable: false),
IsGiven = table.Column<bool>(type: "bit", nullable: false),
CreatedAtUtc = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_RegistryItems", x => x.Id);
table.ForeignKey(
name: "FK_RegistryItems_Registries_RegistryId",
column: x => x.RegistryId,
principalTable: "Registries",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "RegistrySettings",
columns: table => new
{
RegistryId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
BankAccountIban = table.Column<string>(type: "nvarchar(34)", maxLength: 34, nullable: true),
BankAccountBic = table.Column<string>(type: "nvarchar(11)", maxLength: 11, nullable: true),
BankAccountDisplayName = table.Column<string>(type: "nvarchar(120)", maxLength: 120, nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_RegistrySettings", x => x.RegistryId);
table.ForeignKey(
name: "FK_RegistrySettings_Registries_RegistryId",
column: x => x.RegistryId,
principalTable: "Registries",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "RegistryVisits",
columns: table => new
{
RegistryId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
UserId = table.Column<string>(type: "nvarchar(450)", maxLength: 450, nullable: false),
LastVisitedAtUtc = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_RegistryVisits", x => new { x.RegistryId, x.UserId });
table.ForeignKey(
name: "FK_RegistryVisits_Registries_RegistryId",
column: x => x.RegistryId,
principalTable: "Registries",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "ItemContributions",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
RegistryItemId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
UserId = table.Column<string>(type: "nvarchar(450)", maxLength: 450, nullable: false),
Amount = table.Column<decimal>(type: "decimal(18,2)", precision: 18, scale: 2, nullable: false),
CurrencyCode = table.Column<string>(type: "nvarchar(3)", maxLength: 3, nullable: false),
TransferMessage = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: false),
ContributedAtUtc = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ItemContributions", x => x.Id);
table.ForeignKey(
name: "FK_ItemContributions_RegistryItems_RegistryItemId",
column: x => x.RegistryItemId,
principalTable: "RegistryItems",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "ItemPurchases",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
RegistryItemId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
UserId = table.Column<string>(type: "nvarchar(450)", maxLength: 450, nullable: false),
Quantity = table.Column<int>(type: "int", nullable: false),
PurchasedAtUtc = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ItemPurchases", x => x.Id);
table.ForeignKey(
name: "FK_ItemPurchases_RegistryItems_RegistryItemId",
column: x => x.RegistryItemId,
principalTable: "RegistryItems",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_ItemContributions_RegistryItemId",
table: "ItemContributions",
column: "RegistryItemId");
migrationBuilder.CreateIndex(
name: "IX_ItemPurchases_RegistryItemId",
table: "ItemPurchases",
column: "RegistryItemId");
migrationBuilder.CreateIndex(
name: "IX_PlatformOwners_UserId",
table: "PlatformOwners",
column: "UserId",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_Registries_PublicLinkCode",
table: "Registries",
column: "PublicLinkCode",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_RegistryAdminInvites_RegistryId",
table: "RegistryAdminInvites",
column: "RegistryId");
migrationBuilder.CreateIndex(
name: "IX_RegistryAdminInvites_Token",
table: "RegistryAdminInvites",
column: "Token",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_RegistryItems_RegistryId",
table: "RegistryItems",
column: "RegistryId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "ItemContributions");
migrationBuilder.DropTable(
name: "ItemPurchases");
migrationBuilder.DropTable(
name: "PlatformOwners");
migrationBuilder.DropTable(
name: "RegistryAdminInvites");
migrationBuilder.DropTable(
name: "RegistryAdmins");
migrationBuilder.DropTable(
name: "RegistryItems");
migrationBuilder.DropTable(
name: "RegistrySettings");
migrationBuilder.DropTable(
name: "RegistryVisits");
migrationBuilder.DropTable(
name: "Registries");
}
}
}
@@ -0,0 +1,559 @@
// <auto-generated />
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("20260519130000_AddHeroImageToRegistry")]
partial class AddHeroImageToRegistry
{
/// <inheritdoc />
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<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<decimal>("Amount")
.HasPrecision(18, 2)
.HasColumnType("decimal(18,2)");
b.Property<DateTimeOffset>("ContributedAtUtc")
.HasColumnType("datetimeoffset");
b.Property<string>("CurrencyCode")
.IsRequired()
.HasMaxLength(3)
.HasColumnType("nvarchar(3)");
b.Property<Guid>("RegistryItemId")
.HasColumnType("uniqueidentifier");
b.Property<string>("TransferMessage")
.IsRequired()
.HasMaxLength(500)
.HasColumnType("nvarchar(500)");
b.Property<string>("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<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<DateTimeOffset>("PurchasedAtUtc")
.HasColumnType("datetimeoffset");
b.Property<int>("Quantity")
.HasColumnType("int");
b.Property<Guid>("RegistryItemId")
.HasColumnType("uniqueidentifier");
b.Property<string>("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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<DateTimeOffset>("AssignedAtUtc")
.HasColumnType("datetimeoffset");
b.Property<string>("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<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<string>("BabyName")
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<DateOnly?>("BirthDate")
.HasColumnType("date");
b.Property<DateTimeOffset>("CreatedAtUtc")
.HasColumnType("datetimeoffset");
b.Property<string>("CurrencyCode")
.IsRequired()
.HasMaxLength(3)
.HasColumnType("nvarchar(3)");
b.Property<string>("HeaderContentHtml")
.HasColumnType("nvarchar(max)");
b.Property<string>("HeroImageContentType")
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<byte[]>("HeroImageData")
.HasColumnType("varbinary(max)");
b.Property<string>("PublicLinkCode")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<int>("RegistryType")
.HasColumnType("int");
b.Property<string>("ShippingAddress")
.HasColumnType("nvarchar(max)");
b.Property<string>("ThemeKey")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<string>("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<Guid>("RegistryId")
.HasColumnType("uniqueidentifier");
b.Property<string>("UserId")
.HasMaxLength(450)
.HasColumnType("nvarchar(450)");
b.Property<DateTimeOffset>("AddedAtUtc")
.HasColumnType("datetimeoffset");
b.HasKey("RegistryId", "UserId");
b.ToTable("RegistryAdmins");
});
modelBuilder.Entity("BirthList.Domain.Entities.RegistryAdminInvite", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<DateTimeOffset>("ExpiresAtUtc")
.HasColumnType("datetimeoffset");
b.Property<DateTimeOffset?>("RedeemedAtUtc")
.HasColumnType("datetimeoffset");
b.Property<Guid>("RegistryId")
.HasColumnType("uniqueidentifier");
b.Property<string>("SentToEmail")
.HasMaxLength(320)
.HasColumnType("nvarchar(320)");
b.Property<string>("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<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<Guid>("CategoryId")
.HasColumnType("uniqueidentifier");
b.Property<DateTimeOffset>("CreatedAtUtc")
.HasColumnType("datetimeoffset");
b.Property<string>("CurrencyCode")
.IsRequired()
.HasMaxLength(3)
.HasColumnType("nvarchar(3)");
b.Property<string>("Description")
.HasColumnType("nvarchar(max)");
b.Property<int>("DesiredQuantity")
.HasColumnType("int");
b.Property<bool>("IsGiven")
.HasColumnType("bit");
b.Property<decimal>("MoneyFulfilledAmount")
.HasPrecision(18, 2)
.HasColumnType("decimal(18,2)");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(300)
.HasColumnType("nvarchar(300)");
b.Property<bool>("ParticipationAllowed")
.HasColumnType("bit");
b.Property<decimal?>("ParticipationTargetAmount")
.HasPrecision(18, 2)
.HasColumnType("decimal(18,2)");
b.Property<string>("PictureUrl")
.HasMaxLength(2048)
.HasColumnType("nvarchar(2048)");
b.Property<bool?>("PreferSecondHand")
.HasColumnType("bit");
b.Property<decimal?>("PriceAmount")
.HasPrecision(18, 2)
.HasColumnType("decimal(18,2)");
b.Property<string>("ProductUrl")
.HasMaxLength(2048)
.HasColumnType("nvarchar(2048)");
b.Property<int>("PurchasedQuantity")
.HasColumnType("int");
b.Property<Guid>("RegistryId")
.HasColumnType("uniqueidentifier");
b.Property<int>("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<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<DateTimeOffset>("CreatedAtUtc")
.HasColumnType("datetimeoffset");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<Guid>("RegistryId")
.HasColumnType("uniqueidentifier");
b.Property<int>("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<Guid>("RegistryId")
.HasColumnType("uniqueidentifier");
b.Property<string>("BankAccountBic")
.HasMaxLength(11)
.HasColumnType("nvarchar(11)");
b.Property<string>("BankAccountDisplayName")
.HasMaxLength(120)
.HasColumnType("nvarchar(120)");
b.Property<string>("BankAccountIban")
.HasMaxLength(34)
.HasColumnType("nvarchar(34)");
b.Property<string>("ContributionAmountQrCodesJson")
.HasMaxLength(4000)
.HasColumnType("nvarchar(4000)");
b.Property<string>("ContributionQrCodeUrl")
.HasMaxLength(2048)
.HasColumnType("nvarchar(2048)");
b.Property<bool>("ShowBankAccountName")
.HasColumnType("bit");
b.HasKey("RegistryId");
b.ToTable("RegistrySettings");
});
modelBuilder.Entity("BirthList.Domain.Entities.RegistryVisit", b =>
{
b.Property<Guid>("RegistryId")
.HasColumnType("uniqueidentifier");
b.Property<string>("UserId")
.HasMaxLength(450)
.HasColumnType("nvarchar(450)");
b.Property<DateTimeOffset>("LastVisitedAtUtc")
.HasColumnType("datetimeoffset");
b.HasKey("RegistryId", "UserId");
b.ToTable("RegistryVisits");
});
modelBuilder.Entity("BirthList.Domain.Entities.UserActionLog", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<int>("ActionType")
.HasColumnType("int");
b.Property<decimal>("Amount")
.HasPrecision(18, 2)
.HasColumnType("decimal(18,2)");
b.Property<DateTimeOffset>("CreatedAtUtc")
.HasColumnType("datetimeoffset");
b.Property<string>("Details")
.HasMaxLength(500)
.HasColumnType("nvarchar(500)");
b.Property<int>("Quantity")
.HasColumnType("int");
b.Property<Guid>("RegistryId")
.HasColumnType("uniqueidentifier");
b.Property<Guid?>("RegistryItemId")
.HasColumnType("uniqueidentifier");
b.Property<string>("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
}
}
}
@@ -0,0 +1,39 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace BirthList.Infrastructure.Migrations
{
/// <inheritdoc />
public partial class AddHeroImageToRegistry : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<byte[]>(
name: "HeroImageData",
table: "Registries",
type: "varbinary(max)",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "HeroImageContentType",
table: "Registries",
type: "nvarchar(100)",
maxLength: 100,
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "HeroImageData",
table: "Registries");
migrationBuilder.DropColumn(
name: "HeroImageContentType",
table: "Registries");
}
}
}
@@ -135,6 +135,13 @@ namespace BirthList.Infrastructure.Migrations
b.Property<string>("HeaderContentHtml")
.HasColumnType("nvarchar(max)");
b.Property<string>("HeroImageContentType")
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<byte[]>("HeroImageData")
.HasColumnType("varbinary(max)");
b.Property<string>("PublicLinkCode")
.IsRequired()
.HasMaxLength(100)
@@ -1,4 +1,5 @@
@page "/"
@rendermode InteractiveServer
@using BirthList.Web.Features.Registries
@@ -121,7 +122,7 @@
protected IReadOnlyList<RegistrySummaryViewModel> MyRegistries { get; private set; } = [];
protected IReadOnlyList<RegistrySummaryViewModel> VisitedRegistries { get; private set; } = [];
protected string? ErrorMessage { get; private set; }
protected bool ShowCreateForm { get; private set; }
protected bool ShowCreateForm { get; set; }
[Inject] private RegistryService RegistryService { get; set; } = null!;
[Inject] private RegistryUserContext RegistryUserContext { get; set; } = null!;
@@ -338,6 +338,28 @@ else
<small class="form-text text-muted">@L["RegistryAdmin.LineBreaksPreserved"]</small>
</div>
<div class="mt-3">
<label class="form-label">@L["RegistryAdmin.HeroImage"]</label>
@if (!string.IsNullOrWhiteSpace(SettingsModel.HeroImageBase64))
{
<div class="mb-2">
<label class="form-label small">@L["RegistryAdmin.CurrentHeroImage"]</label>
<div>
<img src="data:@SettingsModel.HeroImageContentType;base64,@SettingsModel.HeroImageBase64" alt="Hero" class="img-thumbnail" style="max-height: 200px;" />
</div>
<button type="button" class="btn btn-sm btn-outline-danger mt-2" @onclick="RemoveHeroImageAsync">@L["RegistryAdmin.RemoveHeroImage"]</button>
</div>
}
<div>
<label class="form-label small">@L["RegistryAdmin.UploadImage"]</label>
<InputFile class="form-control" OnChange="OnHeroImageSelectedAsync" accept="image/*" />
@if (!string.IsNullOrWhiteSpace(HeroImageUploadMessage))
{
<small class="text-muted">@HeroImageUploadMessage</small>
}
</div>
</div>
<div class="mt-3">
<label class="form-label">@L["RegistryAdmin.TopContent"]</label>
<div class="editor-wrapper">
@@ -5,6 +5,7 @@ using BirthList.Web.Services;
using Blazored.TextEditor;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.JSInterop;
@@ -55,6 +56,7 @@ public partial class RegistryAdmin : ComponentBase
protected string? InviteLink { get; private set; }
protected BlazoredTextEditor? TextEditor { get; set; }
protected string ActiveTab { get; set; } = "items";
protected string? HeroImageUploadMessage { get; set; }
protected RenderFragment ToolbarContent => builder =>
{
builder.AddMarkupContent(0, @"<span class='ql-formats'><select class='ql-size'><option value='small'></option><option selected></option><option value='large'></option><option value='huge'></option></select><select class='ql-header'><option selected></option><option value='1'></option><option value='2'></option><option value='3'></option><option value='4'></option><option value='5'></option></select></span>");
@@ -805,4 +807,48 @@ public partial class RegistryAdmin : ComponentBase
return false;
}
}
protected async Task OnHeroImageSelectedAsync(InputFileChangeEventArgs e)
{
HeroImageUploadMessage = null;
try
{
const long maxFileSize = 10 * 1024 * 1024; // 10 MB
var file = e.File;
if (file.Size > maxFileSize)
{
HeroImageUploadMessage = $"File size exceeds maximum of 10 MB";
return;
}
if (!file.ContentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase))
{
HeroImageUploadMessage = "Only image files are allowed";
return;
}
using var memoryStream = new MemoryStream();
await file.OpenReadStream(maxFileSize).CopyToAsync(memoryStream).ConfigureAwait(false);
SettingsModel.HeroImageBase64 = Convert.ToBase64String(memoryStream.ToArray());
SettingsModel.HeroImageContentType = file.ContentType;
HeroImageUploadMessage = "Image uploaded successfully. Click 'Save Settings' to apply.";
}
catch (Exception ex)
{
HeroImageUploadMessage = $"Error uploading image: {ex.Message}";
}
}
protected Task RemoveHeroImageAsync()
{
SettingsModel.HeroImageBase64 = string.Empty;
SettingsModel.HeroImageContentType = null;
HeroImageUploadMessage = "Image will be removed when you save settings.";
return Task.CompletedTask;
}
}
@@ -12,6 +12,13 @@
else
{
<div class="registry-shell @RegistryThemeService.GetCssClass(Registry.RegistryType, Registry.ThemeKey)">
@if (!string.IsNullOrWhiteSpace(Registry.HeroImageBase64))
{
<div class="hero-image-container mb-4">
<img src="data:@Registry.HeroImageContentType;base64,@Registry.HeroImageBase64" alt="@Registry.Title" class="hero-image img-fluid w-100" />
</div>
}
<h1>@(string.IsNullOrWhiteSpace(Registry.BabyName) ? Registry.Title : Registry.BabyName)</h1>
@if (!string.IsNullOrWhiteSpace(Registry.HeaderContentHtml))
@@ -4,6 +4,20 @@
gap: 1rem;
}
.hero-image-container {
margin: 0 -1rem;
max-height: 400px;
overflow: hidden;
border-radius: 8px;
}
.hero-image {
object-fit: cover;
object-position: center;
max-height: 400px;
border-radius: 8px;
}
.item-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
@@ -53,6 +53,8 @@ public sealed class RegistrySettingsEditModel
{
public string? BabyName { get; set; }
public DateOnly? BirthDate { get; set; }
public string? HeroImageBase64 { get; set; }
public string? HeroImageContentType { get; set; }
public string? HeaderContentHtml { get; set; }
public string? ShippingAddress { get; set; }
public string CurrencyCode { get; set; } = "€";
@@ -80,6 +82,8 @@ public sealed class RegistryPublicViewModel
public string Title { get; init; } = string.Empty;
public string PublicLinkCode { get; init; } = string.Empty;
public string? BabyName { get; init; }
public string? HeroImageBase64 { get; init; }
public string? HeroImageContentType { get; init; }
public string? HeaderContentHtml { get; init; }
public string? ShippingAddress { get; init; }
public string CurrencyCode { get; init; } = "€";
@@ -299,6 +299,8 @@ internal sealed class RegistryService(RegistryDbContext registryDbContext, Appli
Title = registry.Title,
PublicLinkCode = registry.PublicLinkCode,
BabyName = registry.BabyName,
HeroImageBase64 = registry.HeroImageData != null ? Convert.ToBase64String(registry.HeroImageData) : null,
HeroImageContentType = registry.HeroImageContentType,
HeaderContentHtml = registry.HeaderContentHtml,
ShippingAddress = registry.ShippingAddress,
CurrencyCode = registry.CurrencyCode,
@@ -336,6 +338,8 @@ internal sealed class RegistryService(RegistryDbContext registryDbContext, Appli
{
BabyName = registry.BabyName,
BirthDate = registry.BirthDate,
HeroImageBase64 = registry.HeroImageData != null ? Convert.ToBase64String(registry.HeroImageData) : null,
HeroImageContentType = registry.HeroImageContentType,
HeaderContentHtml = registry.HeaderContentHtml,
ShippingAddress = registry.ShippingAddress,
CurrencyCode = registry.CurrencyCode,
@@ -372,6 +376,18 @@ 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))
{
registry.HeroImageData = Convert.FromBase64String(model.HeroImageBase64);
registry.HeroImageContentType = model.HeroImageContentType;
}
else if (string.IsNullOrWhiteSpace(model.HeroImageBase64) && model.HeroImageBase64 == string.Empty)
{
registry.HeroImageData = null;
registry.HeroImageContentType = null;
}
registry.HeaderContentHtml = string.IsNullOrWhiteSpace(model.HeaderContentHtml) ? null : model.HeaderContentHtml;
registry.ShippingAddress = string.IsNullOrWhiteSpace(model.ShippingAddress) ? null : model.ShippingAddress.Trim();
registry.CurrencyCode = NormalizeCurrencySymbol(model.CurrencyCode);
@@ -175,6 +175,10 @@
<data name="RegistryAdmin.Theme.Modern" xml:space="preserve"><value>Moderne</value></data>
<data name="RegistryAdmin.ShippingAddress" xml:space="preserve"><value>Adresse de livraison</value></data>
<data name="RegistryAdmin.LineBreaksPreserved" xml:space="preserve"><value>Les sauts de ligne seront conservés</value></data>
<data name="RegistryAdmin.HeroImage" xml:space="preserve"><value>Image héros</value></data>
<data name="RegistryAdmin.UploadImage" xml:space="preserve"><value>Télécharger une image</value></data>
<data name="RegistryAdmin.RemoveHeroImage" xml:space="preserve"><value>Supprimer l'image héros</value></data>
<data name="RegistryAdmin.CurrentHeroImage" xml:space="preserve"><value>Image héros actuelle</value></data>
<data name="RegistryAdmin.TopContent" xml:space="preserve"><value>Contenu d'en-tête</value></data>
<data name="RegistryAdmin.WelcomeText" xml:space="preserve"><value>Texte d'accueil</value></data>
<data name="RegistryAdmin.BankAccountSettings" xml:space="preserve"><value>Paramètres du compte bancaire</value></data>
@@ -175,6 +175,10 @@
<data name="RegistryAdmin.Theme.Modern" xml:space="preserve"><value>Modern</value></data>
<data name="RegistryAdmin.ShippingAddress" xml:space="preserve"><value>Leveringsadres</value></data>
<data name="RegistryAdmin.LineBreaksPreserved" xml:space="preserve"><value>Regeleinden blijven behouden</value></data>
<data name="RegistryAdmin.HeroImage" xml:space="preserve"><value>Hero-afbeelding</value></data>
<data name="RegistryAdmin.UploadImage" xml:space="preserve"><value>Afbeelding uploaden</value></data>
<data name="RegistryAdmin.RemoveHeroImage" xml:space="preserve"><value>Hero-afbeelding verwijderen</value></data>
<data name="RegistryAdmin.CurrentHeroImage" xml:space="preserve"><value>Huidige hero-afbeelding</value></data>
<data name="RegistryAdmin.TopContent" xml:space="preserve"><value>Bovenste inhoud</value></data>
<data name="RegistryAdmin.WelcomeText" xml:space="preserve"><value>Welkomsttekst</value></data>
<data name="RegistryAdmin.BankAccountSettings" xml:space="preserve"><value>Bankrekeninginstellingen</value></data>
@@ -501,6 +501,18 @@
<data name="RegistryAdmin.LineBreaksPreserved" xml:space="preserve">
<value>[[Line breaks will be preserved]]</value>
</data>
<data name="RegistryAdmin.HeroImage" xml:space="preserve">
<value>[[Hero image]]</value>
</data>
<data name="RegistryAdmin.UploadImage" xml:space="preserve">
<value>[[Upload image]]</value>
</data>
<data name="RegistryAdmin.RemoveHeroImage" xml:space="preserve">
<value>[[Remove hero image]]</value>
</data>
<data name="RegistryAdmin.CurrentHeroImage" xml:space="preserve">
<value>[[Current hero image]]</value>
</data>
<data name="RegistryAdmin.TopContent" xml:space="preserve">
<value>[[Top content]]</value>
</data>
@@ -182,6 +182,10 @@
<data name="RegistryAdmin.Theme.Modern" xml:space="preserve"><value>Modern</value></data>
<data name="RegistryAdmin.ShippingAddress" xml:space="preserve"><value>Shipping address</value></data>
<data name="RegistryAdmin.LineBreaksPreserved" xml:space="preserve"><value>Line breaks will be preserved</value></data>
<data name="RegistryAdmin.HeroImage" xml:space="preserve"><value>Hero image</value></data>
<data name="RegistryAdmin.UploadImage" xml:space="preserve"><value>Upload image</value></data>
<data name="RegistryAdmin.RemoveHeroImage" xml:space="preserve"><value>Remove hero image</value></data>
<data name="RegistryAdmin.CurrentHeroImage" xml:space="preserve"><value>Current hero image</value></data>
<data name="RegistryAdmin.TopContent" xml:space="preserve"><value>Top content</value></data>
<data name="RegistryAdmin.WelcomeText" xml:space="preserve"><value>Welcome text</value></data>
<data name="RegistryAdmin.BankAccountSettings" xml:space="preserve"><value>Bank Account Settings</value></data>