99 Section page: <SubNav Section="@sectionData" RssFeedUrl="/ai/feed.xml" ShowNewsletter="true" />
1010 Homepage: <SubNav Sections="@allSections" RssFeedUrl="/all/feed.xml" ShowNewsletter="true" />
1111 About page: <SubNav Sections="@allSections" HideStats="true" ShowDiscord="true" />
12- Newsletter: <SubNav Sections="@allSections" ShowHomepageButton="true" />
12+ Newsletter: <SubNav Sections="@allSections" />
1313 ============================================================================ *@
1414
1515@using TechHub .Core .Models
1616@using TechHub .Web .Services
1717@inject NavigationManager Navigation
1818@inject IJSRuntime JS
1919@inject HomepageStatsState HomepageStats
20+ @inject ITechHubApiClient ApiClient
2021@implements IDisposable
2122
2223@if (Section != null )
8687 </div >
8788 </nav >
8889}
89- else if (Sections != null && Sections . Any () )
90+ else if (Sections != null && HasHomepageContent )
9091{
9192 @* Homepage SubNav: stat buttons + Newsletter; visible on desktop and mobile (no dropdown) *@
9293 <nav class =" sub-nav sub-nav-homepage-visible" aria-label =" Site navigation" >
9394 <div class =" sub-nav-wrapper" >
95+ @if (ShowHomeButton )
96+ {
97+ <a href =" /" class =" btn-subnav @(IsHomepageActive() ? " active " : " " )" aria-label =" Homepage" >
98+ Home
99+ </a >
100+ }
101+ @if (ShowNewsletter )
102+ {
103+ <a href =" /newsletter/subscribe" class =" btn-subnav btn-subnav-action @(IsActive(" /newsletter " ) ? " active " : " " )" aria-label =" Subscribe to newsletter" >
104+ <svg width =" 14" height =" 14" viewBox =" 0 0 24 24" fill =" currentColor" aria-hidden =" true" ><path d =" M20 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 4l-8 5-8-5V6l8 5 8-5v2z" /></svg >
105+ Newsletter
106+ </a >
107+ }
94108 @if (! HideStats && HomepageStats .TotalCount .HasValue )
95109 {
96110 @if (HomepageStats .RecentCount is > 0 )
97111 {
98112 <a href =" /all?from=@HomepageStats.WeekAgoDate" class =" btn-subnav btn-subnav-action btn-subnav-stat-new" aria-label =" @HomepageStats.RecentCount new items this week" >
99- <svg width =" 13" height =" 13" viewBox =" 0 0 24 24" fill =" currentColor" aria-hidden =" true" ><path d =" M13.5 0.67s.74 2.65.74 4.8c0 2.06-1.35 3.73-3.41 3.73-2.07 0-3.63-1.67-3.63-3.73l.03-.36C5.21 7.51 4 10.62 4 14c0 4.42 3.58 8 8 8s8-3.58 8-8C20 8.61 17.41 3.8 13.5.67zM11.71 19c-1.78 0-3.22-1.4-3.22-3.14 0-1.62 1.05-2.76 2.81-3.12 1.77-.36 3.6-1.21 4.62-2.58.39 1.29.59 2.65.59 4.04 0 2.65-2.15 4.8-4.8 4.8z" /></svg ><span >@HomepageStats.RecentCount <span class =" stat-chip-new-label" > new this week </span ></span >
113+ <svg width =" 13" height =" 13" viewBox =" 0 0 24 24" fill =" currentColor" aria-hidden =" true" ><path d =" M13.5 0.67s.74 2.65.74 4.8c0 2.06-1.35 3.73-3.41 3.73-2.07 0-3.63-1.67-3.63-3.73l.03-.36C5.21 7.51 4 10.62 4 14c0 4.42 3.58 8 8 8s8-3.58 8-8C20 8.61 17.41 3.8 13.5.67zM11.71 19c-1.78 0-3.22-1.4-3.22-3.14 0-1.62 1.05-2.76 2.81-3.12 1.77-.36 3.6-1.21 4.62-2.58.39 1.29.59 2.65.59 4.04 0 2.65-2.15 4.8-4.8 4.8z" /></svg ><span >@HomepageStats.RecentCount <span class =" stat-chip-new-label" > new < span class = " stat-chip-new-label-suffix " > this week </ span > </span ></span >
100114 </a >
101115 }
102116 <a href =" /all" class =" btn-subnav btn-subnav-action btn-subnav-stat-total" aria-label =" @HomepageStats.TotalCount items total" >
103117 <svg width =" 13" height =" 13" viewBox =" 0 0 24 24" fill =" currentColor" aria-hidden =" true" ><path d =" M11.99 18.54l-7.37-5.73L3 14.07l9 7 9-7-1.63-1.27-7.38 5.74zM12 16l7.36-5.73L21 9l-9-7-9 7 1.63 1.27L12 16z" /></svg ><span >@HomepageStats.TotalCount.Value.ToString( " N0" , System .Globalization .CultureInfo .InvariantCulture )<span class =" stat-chip-total-label" > items total </span ></span >
104118 </a >
105119 }
106- @if (ShowNewsletter )
107- {
108- <a href =" /newsletter/subscribe" class =" btn-subnav btn-subnav-action btn-subnav-newsletter" aria-label =" Subscribe to newsletter" >
109- <svg width =" 14" height =" 14" viewBox =" 0 0 24 24" fill =" currentColor" aria-hidden =" true" ><path d =" M20 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 4l-8 5-8-5V6l8 5 8-5v2z" /></svg >
110- Newsletter
111- </a >
112- }
113120 @if (ShowDiscord )
114121 {
115122 <a href =" https://discord.gg/cURHV9TvFS" target =" _blank" rel =" noopener noreferrer" class =" btn-subnav btn-subnav-action btn-subnav-discord" aria-label =" Join our Discord server" >
116123 <svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057.1 18.08.114 18.1.132 18.112a19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028c.462-.63.874-1.295 1.226-1.994a.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.946 2.418-2.157 2.418z"/></svg>
117124 Find us on GitHub Community Discord
118125 </a >
119126 }
120- @if (ShowHomepageButton )
121- {
122- <a href =" /" class =" btn-subnav btn-subnav-action btn-subnav-newsletter" >
123- Homepage
124- </a >
125- }
126127 </div >
127128 </nav >
128129}
@@ -144,6 +145,13 @@ else if (Sections != null && Sections.Any())
144145 [Parameter ]
145146 public string ? RssFeedUrl { get ; set ; }
146147
148+ /// <summary >
149+ /// When true, renders a "Home" button to the left of the stats in the homepage-style SubNav.
150+ /// The button is visually highlighted (active) when the user is on the homepage.
151+ /// </summary >
152+ [Parameter ]
153+ public bool ShowHomeButton { get ; set ; }
154+
147155 /// <summary >
148156 /// When true, renders a Newsletter subscribe button in the action area.
149157 /// </summary >
@@ -162,13 +170,13 @@ else if (Sections != null && Sections.Any())
162170 [Parameter ]
163171 public bool ShowDiscord { get ; set ; }
164172
165- /// < summary >
166- /// When true, renders a Homepage button. Used on the Newsletter page for quick back-navigation.
167- /// </ summary >
168- [ Parameter ]
169- public bool ShowHomepageButton { get ; set ; }
170-
171- private bool HasActions => ! string . IsNullOrEmpty ( RssFeedUrl ) || ShowNewsletter ;
173+ private bool HasHomepageActions => ShowHomeButton || ShowNewsletter || ShowDiscord || ! string . IsNullOrEmpty ( RssFeedUrl );
174+ private bool HasHomepageContent => HasHomepageActions || ( ! HideStats && HomepageStats . TotalCount . HasValue );
175+ private bool ShouldFetchHomepageStats => Section == null
176+ && Sections != null
177+ && HasHomepageActions
178+ && ! HideStats
179+ && ! HomepageStats . TotalCount . HasValue ;
172180
173181 private bool dropdownOpen ;
174182
@@ -274,6 +282,32 @@ else if (Sections != null && Sections.Any())
274282 HomepageStats .OnChanged += OnHomepageStatsChanged ;
275283 }
276284
285+ protected override async Task OnParametersSetAsync ()
286+ {
287+ if (ShouldFetchHomepageStats )
288+ {
289+ var weekAgoDate = GetWeekAgoDate ();
290+ var recentItemsTask = ApiClient .GetCollectionItemsAsync (" all" , " all" , take : 1 , fromDate : weekAgoDate );
291+ var allItemsTask = ApiClient .GetCollectionItemsAsync (" all" , " all" , take : 1 );
292+
293+ await Task .WhenAll (recentItemsTask , allItemsTask );
294+
295+ var recentCount = (await recentItemsTask )? .TotalCount ;
296+ var totalCount = (await allItemsTask )? .TotalCount ;
297+
298+ HomepageStats .Update (recentCount , totalCount , weekAgoDate , Sections ? .Count () ?? 0 );
299+ }
300+ }
301+
302+ private static string GetWeekAgoDate ()
303+ {
304+ var brussels = TimeZoneInfo .FindSystemTimeZoneById (" Europe/Brussels" );
305+ return TimeZoneInfo .ConvertTime (DateTimeOffset .UtcNow , brussels )
306+ .Date
307+ .AddDays (- 7 )
308+ .ToString (" yyyy-MM-dd" );
309+ }
310+
277311 private void OnHomepageStatsChanged ()
278312 {
279313 InvokeAsync (StateHasChanged );
@@ -292,6 +326,17 @@ else if (Sections != null && Sections.Any())
292326 HomepageStats .OnChanged -= OnHomepageStatsChanged ;
293327 }
294328
329+ /// <summary >
330+ /// Determines if the user is currently on the homepage (path is exactly "/").
331+ /// Used specifically for the Home button active state; avoids the prefix-match
332+ /// in IsActive which would match "/" against every URL.
333+ /// </summary >
334+ private bool IsHomepageActive ()
335+ {
336+ var currentPath = new Uri (Navigation .Uri ).AbsolutePath .TrimEnd ('/' );
337+ return string .IsNullOrEmpty (currentPath );
338+ }
339+
295340 /// <summary >
296341 /// Determines if the given URL should be visually highlighted in the subnav.
297342 /// Matches both exact pages and content detail pages under a collection.
0 commit comments