Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 14 additions & 15 deletions src/Web/Components/Layout/MainLayout.razor
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
@inherits LayoutComponentBase

<div class="page">
<div class="sidebar">
<NavMenu />
</div>

<main>
<div class="top-row px-4">
<a href="https://learn.microsoft.com/aspnet/core/" target="_blank">About</a>
</div>
<div class="min-h-screen flex flex-col bg-primary-400 dark:bg-primary-800 text-primary-800 dark:text-primary-300">
<NavMenu/>

<article class="content px-4">
@Body
</article>
</main>
<main id="main-content" tabindex="-1" class="mx-auto w-full max-w-7xl flex-1 px-4 pt-20 pb-8 outline-none focus:outline-none focus-visible:outline-none">
@Body
</main>

<footer class="mt-auto">
<div class="mx-auto w-full max-w-7xl px-4">
&copy; @DateTime.Now.Year MyBlog — Training Project
</div>
</footer>
</div>

<div id="blazor-error-ui" data-nosnippet>
An unhandled error has occurred.
<a href="." class="reload">Reload</a>
<span class="dismiss">🗙</span>
An unhandled error has occurred.
<a href="." class="reload">Reload</a>
<span class="dismiss">🗙</span>
</div>
258 changes: 198 additions & 60 deletions src/Web/Components/Layout/NavMenu.razor
Original file line number Diff line number Diff line change
@@ -1,62 +1,200 @@
@using Microsoft.AspNetCore.Components.Authorization
@inject IJSRuntime Js
@inject NavigationManager Nav
@implements IDisposable
@rendermode InteractiveServer

<div class="top-row ps-3 navbar navbar-dark">
<div class="container-fluid">
<a class="navbar-brand" href="">MyBlog</a>
</div>
</div>

<input type="checkbox" title="Navigation menu" class="navbar-toggler" />

<div class="nav-scrollable" onclick="document.querySelector('.navbar-toggler').click()">
<nav class="nav flex-column">
<div class="nav-item px-3">
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
<span class="bi bi-house-door-fill-nav-menu" aria-hidden="true"></span> Home
</NavLink>
</div>

<div class="nav-item px-3">
<NavLink class="nav-link" href="blog">
<span class="bi bi-journal-text-nav-menu" aria-hidden="true"></span> Blog Posts
</NavLink>
</div>

<AuthorizeView Roles="Author,Admin">
<Authorized>
<div class="nav-item px-3">
<NavLink class="nav-link" href="blog/create">
<span class="bi bi-plus-square-fill-nav-menu" aria-hidden="true"></span> New Post
</NavLink>
</div>
</Authorized>
</AuthorizeView>

<AuthorizeView Roles="Admin">
<Authorized>
<div class="nav-item px-3">
<NavLink class="nav-link" href="admin/users">
<span class="bi bi-people-fill-nav-menu" aria-hidden="true"></span> Manage Users
</NavLink>
</div>
</Authorized>
</AuthorizeView>

<AuthorizeView>
<Authorized>
<div class="nav-item px-3">
<a class="nav-link" href="/Account/Logout">
<span class="bi bi-box-arrow-right-nav-menu" aria-hidden="true"></span> Logout (@context.User.Identity?.Name)
</a>
</div>
</Authorized>
<NotAuthorized>
<div class="nav-item px-3">
<a class="nav-link" href="/Account/Login">
<span class="bi bi-box-arrow-in-right-nav-menu" aria-hidden="true"></span> Login
</a>
</div>
</NotAuthorized>
</AuthorizeView>
</nav>
</div>
<nav class="fixed top-0 left-0 right-0 z-50
bg-primary-600 dark:bg-primary-600
border-b-2 border-primary-200 dark:border-primary-200"
aria-label="Main navigation">
<div class="mx-auto max-w-7xl px-4">
<div class="flex items-center justify-between h-16">

@* Brand *@
<NavLink class="nav-link text-xl font-bold" href="" Match="NavLinkMatch.All">MyBlog</NavLink>

@* Mobile hamburger (pure CSS peer toggle — no JS required) *@
<input type="checkbox" id="menu-toggle" class="peer sr-only" aria-hidden="true"/>
<label for="menu-toggle"
class="md:hidden cursor-pointer p-2 rounded hover:bg-primary-500 dark:hover:bg-primary-600 text-white"
aria-label="Toggle navigation">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
Comment on lines +18 to +22

Copilot AI Apr 18, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The mobile nav toggle uses a visually-hidden checkbox with aria-hidden="true" and a <label> as the interactive control. Because the checkbox is hidden from assistive technology and the label isn’t keyboard-focusable by default, this is not accessible for keyboard/screen-reader users. Consider using a real <button> with aria-expanded/aria-controls (or make the control focusable and expose state).

Copilot uses AI. Check for mistakes.
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M4 6h16M4 12h16M4 18h16"/>
</svg>
</label>

@* Desktop nav links *@
<div class="hidden md:flex items-center gap-5">

<NavLink class="nav-link" href="blog">Blog Posts</NavLink>

<AuthorizeView Roles="Author,Admin">
<Authorized>
<NavLink class="nav-link" href="blog/create">New Post</NavLink>
</Authorized>
</AuthorizeView>

<AuthorizeView>
<Authorized>
<NavLink class="nav-link" href="profile">@GetProfileLabel(context.User)</NavLink>
</Authorized>
</AuthorizeView>

<AuthorizeView Roles="Admin">
<Authorized>
<NavLink class="nav-link" href="admin/users">Manage Users</NavLink>
</Authorized>
</AuthorizeView>

@* Theme colour switcher *@
<select @onchange="OnThemeChanged"
class="text-sm rounded border border-primary-200 dark:border-primary-800 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 px-2 py-1 cursor-pointer focus:outline-none focus:ring-2 focus:ring-primary-400"
aria-label="Colour theme" title="Select colour theme">
<option value="red" selected="@(_currentColor == "red")">🔴 Red</option>
<option value="blue" selected="@(_currentColor == "blue")">🔵 Blue</option>
<option value="green" selected="@(_currentColor == "green")">🟢 Green</option>
<option value="yellow" selected="@(_currentColor == "yellow")">🟡 Yellow</option>
</select>

@* Dark mode toggle — sun icon in dark mode, moon in light *@
<button @onclick="ToggleDark"
class="p-1.5 rounded hover:bg-primary-500 dark:hover:bg-primary-600 text-white transition"
aria-label="Toggle dark mode" title="Toggle dark mode">
@if (_currentBrightness == "dark")
{
@* Sun — click to switch to light *@
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" fill="none" viewBox="0 0 24 24"
stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round"
d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"/>
</svg>
}
else
{
@* Moon — click to switch to dark *@
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" fill="none" viewBox="0 0 24 24"
stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round"
d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"/>
</svg>
}
</button>

@* Login / Logout *@
<AuthorizeView>
<Authorized>
<a class="nav-link" href="/Account/Logout">Logout</a>
</Authorized>
<NotAuthorized>
<a class="nav-link" href="/Account/Login">Login</a>
</NotAuthorized>
</AuthorizeView>
</div>
</div>

@* Mobile nav (shown when peer checkbox is checked) *@
<div class="hidden peer-checked:flex flex-col pb-4 md:hidden gap-1"
id="mobile-menu">
<NavLink class="nav-link py-2 block" href="" Match="NavLinkMatch.All">Home</NavLink>

<NavLink class="nav-link py-2 block" href="blog">Blog Posts</NavLink>

<AuthorizeView Roles="Author,Admin">
<Authorized>
<NavLink class="nav-link py-2 block" href="blog/create">New Post</NavLink>
</Authorized>
</AuthorizeView>

<AuthorizeView>
<Authorized>
<NavLink class="nav-link py-2 block" href="profile">@GetProfileLabel(context.User)</NavLink>
</Authorized>
</AuthorizeView>

<AuthorizeView Roles="Admin">
<Authorized>
<NavLink class="nav-link py-2 block" href="admin/users">Manage Users</NavLink>
</Authorized>
</AuthorizeView>

<AuthorizeView>
<Authorized>
<a class="nav-link py-2 block" href="/Account/Logout">Logout</a>
</Authorized>
<NotAuthorized>
<a class="nav-link py-2 block" href="/Account/Login">Login</a>
</NotAuthorized>
</AuthorizeView>
</div>
</div>
</nav>

@code {
private string _currentColor = "blue";
private string _currentBrightness = "light";

protected override void OnInitialized()
{
Nav.LocationChanged += OnLocationChanged;
}

protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await SyncThemeFromJs();
}
}

private async void OnLocationChanged(object? sender, LocationChangedEventArgs e)
{
try
{
await InvokeAsync(SyncThemeFromJs);
Comment on lines +151 to +155

Copilot AI Apr 18, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OnLocationChanged is an async void handler that swallows all exceptions. This can hide real failures (and async void exceptions can crash the renderer in some cases). Consider making the handler synchronous and starting a fire-and-forget task with explicit exception handling/logging, or at least capture/log exceptions in the catch block.

Copilot uses AI. Check for mistakes.
}
catch
{
}
}

private async Task SyncThemeFromJs()
{
try
{
_currentColor = await Js.InvokeAsync<string>("themeManager.getColor");
_currentBrightness = await Js.InvokeAsync<string>("themeManager.getBrightness");
StateHasChanged();
}
catch
{
}
}

private async Task OnThemeChanged(ChangeEventArgs e)
{
_currentColor = e.Value?.ToString() ?? "blue";
await Js.InvokeVoidAsync("themeManager.setColor", _currentColor);
}

private async Task ToggleDark()
{
var newBrightness = _currentBrightness == "light" ? "dark" : "light";
await Js.InvokeVoidAsync("themeManager.setBrightness", newBrightness);
_currentBrightness = await Js.InvokeAsync<string>("themeManager.getBrightness");
StateHasChanged();
}

private static string GetProfileLabel(System.Security.Claims.ClaimsPrincipal user)
{
var name = user.Identity?.Name;
return string.IsNullOrWhiteSpace(name) ? "Profile" : name;
}

public void Dispose()
{
Nav.LocationChanged -= OnLocationChanged;
}

}
4 changes: 2 additions & 2 deletions src/Web/Components/Pages/Home.razor
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@

<PageTitle>Home</PageTitle>

<h1>Hello, world!</h1>
<h1>Hello, users!</h1>

Welcome to your new app.
<p>Welcome to your new app.</p>
Loading