- }
-
+
diff --git a/Elternbeirat.Web/Features/Home/Home.razor.cs b/Elternbeirat.Web/Features/Home/Home.razor.cs
index dfc36f9..e5739c1 100644
--- a/Elternbeirat.Web/Features/Home/Home.razor.cs
+++ b/Elternbeirat.Web/Features/Home/Home.razor.cs
@@ -1,5 +1,3 @@
-using System.Globalization;
-using Elternbeirat.Contracts;
using Elternbeirat.PocketBase;
using Microsoft.AspNetCore.Components;
@@ -7,9 +5,7 @@ namespace Elternbeirat.Web.Features.Home;
public partial class Home
{
- private static readonly CultureInfo German = CultureInfo.GetCultureInfo("de-DE");
-
- [LoggerMessage(Level = LogLevel.Warning, Message = "Could not load posts for the home teaser from PocketBase.")]
+ [LoggerMessage(Level = LogLevel.Warning, Message = "Could not load the home page from PocketBase.")]
private static partial void LogLoadFailed(ILogger logger, Exception exception);
[Inject]
@@ -18,19 +14,22 @@ public partial class Home
[Inject]
private ILogger Logger { get; set; } = default!;
- private IReadOnlyList _latest = [];
+ // 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 _embed = [];
protected override async Task OnInitializedAsync()
{
try
{
- // Show the three most recent posts as a teaser on the home page.
- var posts = await PocketBase.GetPostsAsync();
- _latest = [.. posts.Take(3)];
+ var pages = await PocketBase.GetPagesAsync();
+ var home = pages.FirstOrDefault(
+ p => string.Equals(p.Slug, "home", StringComparison.Ordinal));
+ _embed = home?.Embed ?? [];
}
catch (HttpRequestException ex)
{
- // Source unreachable -> show no teaser instead of a 500.
+ // Source unreachable -> render the intro without embeds instead of a 500.
LogLoadFailed(Logger, ex);
}
}
diff --git a/Elternbeirat.Web/Features/Home/Home.razor.css b/Elternbeirat.Web/Features/Home/Home.razor.css
index 016ae11..8a7e962 100644
--- a/Elternbeirat.Web/Features/Home/Home.razor.css
+++ b/Elternbeirat.Web/Features/Home/Home.razor.css
@@ -2,28 +2,3 @@
font-size: 1.1rem;
color: #444;
}
-
-.home-news {
- margin-top: 2rem;
-}
-
-.post-list {
- list-style: none;
- padding: 0;
-}
-
-.post-list li {
- padding: 0.75rem 0;
- border-bottom: 1px solid #eee;
-}
-
-.post-list a {
- font-size: 1.1rem;
- text-decoration: none;
-}
-
-.post-list time {
- display: block;
- color: #777;
- font-size: 0.85rem;
-}
diff --git a/Elternbeirat.Web/Features/Layout/NavBuilder.cs b/Elternbeirat.Web/Features/Layout/NavBuilder.cs
new file mode 100644
index 0000000..08414fc
--- /dev/null
+++ b/Elternbeirat.Web/Features/Layout/NavBuilder.cs
@@ -0,0 +1,31 @@
+using Elternbeirat.Contracts;
+
+namespace Elternbeirat.Web.Features.Layout;
+
+///
+/// Builds the header and footer navigation from the public pages. Pages carry a
+/// ("header"/"footer"/"") and an ;
+/// pages without a location are sub-pages and never appear in a menu.
+///
+public static class NavBuilder
+{
+ /// Header links, ordered, built from pages with location "header".
+ public static IReadOnlyList Header(IReadOnlyList pages) =>
+ Build(pages, "header");
+
+ /// Footer links, ordered, built from pages with location "footer".
+ public static IReadOnlyList Footer(IReadOnlyList pages) =>
+ Build(pages, "footer");
+
+ private static List Build(IReadOnlyList pages, string location) =>
+ [.. pages
+ .Where(page => string.Equals(page.Location, location, StringComparison.Ordinal))
+ .OrderBy(page => page.Order)
+ .Select(page => new NavLinkItem(HrefFor(page.Slug), page.Title))];
+
+ // The home page owns "/" (served by the Home component); every other slug is
+ // reached at "/{slug}", where either a literal route or the catch-all content
+ // page renders it.
+ private static string HrefFor(string slug) =>
+ string.Equals(slug, "home", StringComparison.Ordinal) ? "/" : $"/{slug}";
+}
diff --git a/Elternbeirat.Web/Features/Layout/NavLinkItem.cs b/Elternbeirat.Web/Features/Layout/NavLinkItem.cs
new file mode 100644
index 0000000..4ec5090
--- /dev/null
+++ b/Elternbeirat.Web/Features/Layout/NavLinkItem.cs
@@ -0,0 +1,6 @@
+namespace Elternbeirat.Web.Features.Layout;
+
+/// A single navigation entry derived from a page record.
+/// Target URL; the "home" page maps to "/".
+/// Link text shown to the visitor.
+public sealed record NavLinkItem(string Href, string Title);
diff --git a/Elternbeirat.Web/Features/Pages/ContentPage.razor b/Elternbeirat.Web/Features/Pages/ContentPage.razor
index 63adb59..69f6617 100644
--- a/Elternbeirat.Web/Features/Pages/ContentPage.razor
+++ b/Elternbeirat.Web/Features/Pages/ContentPage.razor
@@ -10,4 +10,5 @@ else
@Body
+
}
diff --git a/Elternbeirat.Web/Shared/EventFormat.cs b/Elternbeirat.Web/Shared/EventFormat.cs
new file mode 100644
index 0000000..274e8ea
--- /dev/null
+++ b/Elternbeirat.Web/Shared/EventFormat.cs
@@ -0,0 +1,48 @@
+using System.Globalization;
+using Elternbeirat.Contracts;
+
+namespace Elternbeirat.Web.Shared;
+
+///
+/// Formats an 's date range for display in German. Shared by
+/// the full event list and the home-page events embed so both render dates the
+/// same way.
+///
+public static class EventFormat
+{
+ private static readonly CultureInfo German = CultureInfo.GetCultureInfo("de-DE");
+
+ // An event counts as all-day when its start carries no wall-clock time
+ // (00:00); PocketBase has no separate flag.
+ private static bool HasTime(Event ev) => ev.Start.TimeOfDay != TimeSpan.Zero;
+
+ ///
+ /// Formats the entry's date range: a single day, a date with a time, or a
+ /// span across days.
+ ///
+ public static string Range(Event ev)
+ {
+ var start = HasTime(ev)
+ ? ev.Start.ToString("dddd, d. MMMM yyyy, HH:mm", German) + " Uhr"
+ : ev.Start.ToString("dddd, d. MMMM yyyy", German);
+
+ if (ev.End is not { } end)
+ {
+ return start;
+ }
+
+ // Same day: append just the end time. Different days: append the full
+ // end date.
+ if (end.Date == ev.Start.Date)
+ {
+ return HasTime(ev)
+ ? start + "–" + end.ToString("HH:mm", German) + " Uhr"
+ : start;
+ }
+
+ var endText = HasTime(ev)
+ ? end.ToString("d. MMMM yyyy, HH:mm", German) + " Uhr"
+ : end.ToString("d. MMMM yyyy", German);
+ return start + " – " + endText;
+ }
+}
diff --git a/Elternbeirat.Web/_Imports.razor b/Elternbeirat.Web/_Imports.razor
index cf7ad57..403085e 100644
--- a/Elternbeirat.Web/_Imports.razor
+++ b/Elternbeirat.Web/_Imports.razor
@@ -8,3 +8,5 @@
@using Microsoft.JSInterop
@using Elternbeirat.Web
@using Elternbeirat.Web.Shared
+@using Elternbeirat.Web.Features.Layout
+@using Elternbeirat.Web.Features.Embeds