Add home hero with intro and buttons from the body, next-event card and page tiles

This commit is contained in:
tleininger committed 2026-10-01 14:05:25 +02:00
1 parent b737f57ef7
commit e51afeb34b
23 files changed
+1262 -65

No files matched your search

+60 -10
View File
@@ -1,5 +1,7 @@
using Elternbeirat.Contracts;
using Elternbeirat.PocketBase;
using Elternbeirat.Web.Features.Events;
using Elternbeirat.Web.Shared;
using Microsoft.AspNetCore.Components;
namespace Elternbeirat.Web.Features.Home;
@@ -8,8 +10,15 @@ public partial class Home
{
[LoggerMessage(
Level = LogLevel.Warning,
Message = "Could not load the home page from PocketBase; embeds are omitted.")]
private static partial void LogSourceUnavailable(
Message = "Could not load the pages for the home page from PocketBase; showing the static title only.")]
private static partial void LogPagesUnavailable(
ILogger logger,
Exception exception);
[LoggerMessage(
Level = LogLevel.Warning,
Message = "Could not load events for the home page from PocketBase; the next-event card is omitted.")]
private static partial void LogEventsUnavailable(
ILogger logger,
Exception exception);
@@ -19,26 +28,67 @@ public partial class Home
[Inject]
private ILogger<Home> Logger { get; set; } = null!;
[Inject]
private TimeProvider Time { get; set; } = null!;
[CascadingParameter]
private HttpContext? HttpContext { get; set; }
// Which dynamic blocks the home page shows is editorial: it comes from the
// "home" page's embed field in PocketBase, not from hard-coded markup.
private IReadOnlyList<string> _embed = [];
// Heading, intro and buttons come from the "home" page's body. Until it is loaded
// -- and for good if PocketBase is down -- the static fallback heading stands, so
// the start page always has a title.
private HomeIntro _intro = HomeIntro.Fallback;
protected override async Task OnInitializedAsync()
// The hero, the tiles and the next-event card are fixed structure of the start
// page; which dynamic blocks follow below is editorial and comes from the "home"
// page's embed field.
private IReadOnlyList<HomeTile> _tiles = [];
private IReadOnlyList<string> _embed = [];
private Event? _nextEvent;
// Month and day for the calendar sheet. The sheet is decoration (the full date is
// in <EventDate> next to it), so the short month drops its trailing period.
private string NextEventMonth =>
_nextEvent?.Start.ToString("MMM", Cultures.German).TrimEnd('.') ?? "";
private string NextEventDay =>
_nextEvent?.Start.Day.ToString(Cultures.German) ?? "";
protected override Task OnInitializedAsync()
{
var token = HttpContext?.RequestAborted ?? CancellationToken.None;
return Task.WhenAll(LoadPagesAsync(token), LoadNextEventAsync(token));
}
// Pages and events load side by side and fail on their own: without events the
// page loses only the card, without pages only text and tiles. Either way the
// start page stays a 200 rather than a 503 -- it is degraded, not broken.
private async Task LoadPagesAsync(CancellationToken token)
{
try
{
var pages = await PocketBase.GetPagesAsync(HttpContext?.RequestAborted ?? default);
var pages = await PocketBase.GetPagesAsync(token);
var home = pages.FirstOrDefault(page => Page.IsHomeSlug(page.Slug));
_intro = HomeIntro.Parse(home?.Body);
_embed = home?.Embed ?? [];
_tiles = HomeTile.From(pages);
}
catch (PocketBaseUnavailableException exception)
{
// Only the embeds fail: render the static intro without them instead of a
// 500. The home page's own text is markup, so it stays intact.
LogSourceUnavailable(Logger, exception);
LogPagesUnavailable(Logger, exception);
}
}
private async Task LoadNextEventAsync(CancellationToken token)
{
try
{
var events = await PocketBase.GetEventsAsync(token);
_nextEvent = UpcomingEvents.From(events, BerlinClock.Today(Time)).FirstOrDefault();
}
catch (PocketBaseUnavailableException exception)
{
LogEventsUnavailable(Logger, exception);
}
}
}