feat: Integrate MudBlazor component library and refactor the base UI layout and home page.

This commit is contained in:
Ho Ngoc Hai
2026-01-19 09:46:47 +07:00
parent f12e2b2465
commit ab9d202c6e
7 changed files with 169 additions and 93 deletions

View File

@@ -1,60 +1,66 @@
@inherits LayoutComponentBase
@inject IJSRuntime JS
@*
EN: Main layout with sidebar navigation and theme toggle.
VI: Layout chính với sidebar navigation và theme toggle.
*@
<MudThemeProvider @bind-IsDarkMode="@_isDarkMode" Theme="_theme" />
<MudPopoverProvider />
<MudDialogProvider />
<MudSnackbarProvider />
<div class="page" data-theme="@currentTheme">
<aside class="sidebar">
<MudLayout>
<MudAppBar Elevation="1" Color="Color.Primary">
<MudIconButton Icon="@Icons.Material.Filled.Menu" Color="Color.Inherit" Edge="Edge.Start" OnClick="@ToggleDrawer" />
<MudText Typo="Typo.h5" Class="ml-3">GoodGo</MudText>
<MudSpacer />
<MudIconButton Icon="@(_isDarkMode ? Icons.Material.Filled.LightMode : Icons.Material.Filled.DarkMode)"
Color="Color.Inherit"
OnClick="@ToggleDarkMode" />
</MudAppBar>
<MudDrawer @bind-Open="_drawerOpen" ClipMode="DrawerClipMode.Always" Elevation="2">
<NavMenu />
</aside>
</MudDrawer>
<main>
<header class="top-row">
<button class="theme-toggle" @onclick="ToggleTheme" title="Toggle theme / Đổi theme">
@if (currentTheme == "dark")
{
<span>☀️ Light</span>
}
else
{
<span>🌙 Dark</span>
}
</button>
</header>
<article class="content">
<MudMainContent Class="pt-16 px-4">
<MudContainer MaxWidth="MaxWidth.Large" Class="my-4">
@Body
</article>
</main>
</div>
</MudContainer>
</MudMainContent>
</MudLayout>
@code {
private string currentTheme = "light";
private bool _drawerOpen = true;
private bool _isDarkMode = true;
protected override async Task OnAfterRenderAsync(bool firstRender)
private MudTheme _theme = new()
{
if (firstRender)
PaletteLight = new PaletteLight()
{
// EN: Load saved theme from localStorage
// VI: Tải theme đã lưu từ localStorage
var savedTheme = await JS.InvokeAsync<string>("localStorage.getItem", "theme");
if (!string.IsNullOrEmpty(savedTheme))
{
currentTheme = savedTheme;
StateHasChanged();
}
}
Primary = "#7c3aed",
Secondary = "#06b6d4",
Tertiary = "#f59e0b",
AppbarBackground = "#7c3aed",
},
PaletteDark = new PaletteDark()
{
Primary = "#7c3aed",
Secondary = "#06b6d4",
Tertiary = "#f59e0b",
AppbarBackground = "#1e1e2e",
Background = "#1e1e2e",
Surface = "#313244",
DrawerBackground = "#181825",
DrawerText = "#cdd6f4",
TextPrimary = "#cdd6f4",
TextSecondary = "#a6adc8",
},
};
private void ToggleDrawer()
{
_drawerOpen = !_drawerOpen;
}
private async Task ToggleTheme()
private void ToggleDarkMode()
{
currentTheme = currentTheme == "dark" ? "light" : "dark";
// EN: Save theme to localStorage
// VI: Lưu theme vào localStorage
await JS.InvokeVoidAsync("localStorage.setItem", "theme", currentTheme);
_isDarkMode = !_isDarkMode;
}
}

View File

@@ -1,30 +1,17 @@
@*
EN: Navigation menu with Products and Auth links.
VI: Menu navigation với các link Products và Auth.
*@
<div class="nav-brand">
GoodGo
</div>
<nav class="nav-menu">
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
<span>🏠</span> Home
</NavLink>
<NavLink class="nav-link" href="products">
<span>📦</span> Products / Sản phẩm
</NavLink>
<NavLink class="nav-link" href="auth">
<span>🔐</span> Auth / Xác thực
</NavLink>
<NavLink class="nav-link" href="counter">
<span></span> Counter
</NavLink>
<NavLink class="nav-link" href="weather">
<span>🌤️</span> Weather
</NavLink>
</nav>
<MudNavMenu>
<MudNavLink Href="/" Match="NavLinkMatch.All" Icon="@Icons.Material.Filled.Home">
Home
</MudNavLink>
<MudNavLink Href="/products" Match="NavLinkMatch.Prefix" Icon="@Icons.Material.Filled.ShoppingCart">
Products
</MudNavLink>
<MudNavLink Href="/auth" Match="NavLinkMatch.Prefix" Icon="@Icons.Material.Filled.Person">
Auth
</MudNavLink>
<MudNavLink Href="/counter" Match="NavLinkMatch.Prefix" Icon="@Icons.Material.Filled.Add">
Counter
</MudNavLink>
<MudNavLink Href="/weather" Match="NavLinkMatch.Prefix" Icon="@Icons.Material.Filled.Cloud">
Weather
</MudNavLink>
</MudNavMenu>

View File

@@ -1,7 +1,71 @@
@page "/"
<PageTitle>Home</PageTitle>
<PageTitle>Home - GoodGo</PageTitle>
<h1>Hello, world!</h1>
<MudText Typo="Typo.h3" GutterBottom="true">Welcome to GoodGo</MudText>
<MudText Typo="Typo.body1" Class="mb-4">
Enterprise frontend application built with Blazor WebAssembly and MudBlazor.
</MudText>
Welcome to your new app.
<MudGrid>
<MudItem xs="12" sm="6" md="4">
<MudCard Elevation="2">
<MudCardHeader>
<CardHeaderAvatar>
<MudAvatar Color="Color.Primary">
<MudIcon Icon="@Icons.Material.Filled.ShoppingCart" />
</MudAvatar>
</CardHeaderAvatar>
<CardHeaderContent>
<MudText Typo="Typo.h6">Products</MudText>
<MudText Typo="Typo.body2">Manage your product catalog</MudText>
</CardHeaderContent>
</MudCardHeader>
<MudCardActions>
<MudButton Variant="Variant.Text" Color="Color.Primary" Href="/products">View Products</MudButton>
</MudCardActions>
</MudCard>
</MudItem>
<MudItem xs="12" sm="6" md="4">
<MudCard Elevation="2">
<MudCardHeader>
<CardHeaderAvatar>
<MudAvatar Color="Color.Secondary">
<MudIcon Icon="@Icons.Material.Filled.Person" />
</MudAvatar>
</CardHeaderAvatar>
<CardHeaderContent>
<MudText Typo="Typo.h6">Authentication</MudText>
<MudText Typo="Typo.body2">Login and registration</MudText>
</CardHeaderContent>
</MudCardHeader>
<MudCardActions>
<MudButton Variant="Variant.Text" Color="Color.Secondary" Href="/auth">Go to Auth</MudButton>
</MudCardActions>
</MudCard>
</MudItem>
<MudItem xs="12" sm="6" md="4">
<MudCard Elevation="2">
<MudCardHeader>
<CardHeaderAvatar>
<MudAvatar Color="Color.Tertiary">
<MudIcon Icon="@Icons.Material.Filled.Api" />
</MudAvatar>
</CardHeaderAvatar>
<CardHeaderContent>
<MudText Typo="Typo.h6">BFF Architecture</MudText>
<MudText Typo="Typo.body2">YARP reverse proxy to microservices</MudText>
</CardHeaderContent>
</MudCardHeader>
<MudCardActions>
<MudButton Variant="Variant.Text" Color="Color.Tertiary" Href="/swagger">API Docs</MudButton>
</MudCardActions>
</MudCard>
</MudItem>
</MudGrid>
<MudAlert Severity="Severity.Info" Class="mt-4">
<MudText>This app uses the <strong>Backend for Frontend (BFF)</strong> pattern with YARP to proxy API calls to microservices.</MudText>
</MudAlert>

View File

@@ -1,11 +1,18 @@
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using MudBlazor.Services;
using WebClientBase.Client;
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");
// EN: Add HttpClient for API calls
// VI: Thêm HttpClient cho các cuộc gọi API
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
// EN: Add MudBlazor services
// VI: Thêm các services của MudBlazor
builder.Services.AddMudServices();
await builder.Build().RunAsync();

View File

@@ -10,6 +10,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="10.0.1" PrivateAssets="all" />
<PackageReference Include="MudBlazor" Version="8.15.0" />
</ItemGroup>
<ItemGroup>

View File

@@ -6,8 +6,8 @@
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.AspNetCore.Components.WebAssembly.Http
@using Microsoft.JSInterop
@using MudBlazor
@using WebClientBase.Client
@using WebClientBase.Client.Layout
@using WebClientBase.Shared
@using WebClientBase.Shared.DTOs

View File

@@ -7,14 +7,20 @@
<title>GoodGo Web Client Base</title>
<base href="/" />
<!-- EN: Google Fonts - Inter for modern typography -->
<!-- VI: Google Fonts - Inter cho typography hiện đại -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<!-- EN: Google Fonts - Roboto for MudBlazor -->
<!-- VI: Google Fonts - Roboto cho MudBlazor -->
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet" />
<!-- EN: Design System CSS -->
<!-- VI: CSS Design System -->
<!-- EN: Material Design Icons -->
<!-- VI: Icons Material Design -->
<link href="https://fonts.googleapis.com/css?family=Material+Icons|Material+Icons+Outlined|Material+Icons+Two+Tone|Material+Icons+Round|Material+Icons+Sharp" rel="stylesheet">
<!-- EN: MudBlazor CSS -->
<!-- VI: CSS MudBlazor -->
<link href="_content/MudBlazor/MudBlazor.min.css" rel="stylesheet" />
<!-- EN: Custom CSS -->
<!-- VI: CSS tùy chỉnh -->
<link rel="stylesheet" href="css/app.css" />
<link rel="icon" type="image/png" href="favicon.png" />
<link href="WebClientBase.Client.styles.css" rel="stylesheet" />
@@ -22,17 +28,17 @@
<body>
<div id="app">
<!-- EN: Loading indicator -->
<!-- VI: Chỉ báo đang tải -->
<div style="display: flex; justify-content: center; align-items: center; height: 100vh; flex-direction: column; gap: 1rem;">
<!-- EN: MudBlazor Loading indicator -->
<!-- VI: Chỉ báo đang tải MudBlazor -->
<div style="display: flex; justify-content: center; align-items: center; height: 100vh; flex-direction: column; gap: 1rem; background: #1e1e2e;">
<svg class="loading-progress" width="80" height="80" viewBox="0 0 80 80">
<circle cx="40" cy="40" r="32" fill="none" stroke="#e5e5ea" stroke-width="4" />
<circle cx="40" cy="40" r="32" fill="none" stroke="#3b82f6" stroke-width="4"
<circle cx="40" cy="40" r="32" fill="none" stroke="#313244" stroke-width="4" />
<circle cx="40" cy="40" r="32" fill="none" stroke="#7c3aed" stroke-width="4"
stroke-dasharray="200" stroke-dashoffset="60"
style="animation: spin 1s linear infinite; transform-origin: center;">
</circle>
</svg>
<p style="color: #6e6e73; font-family: Inter, sans-serif;">Loading GoodGo... / Đang tải...</p>
<p style="color: #a6adc8; font-family: Roboto, sans-serif;">Loading GoodGo... / Đang tải...</p>
</div>
<style>
@@ -42,13 +48,18 @@
</style>
</div>
<div id="blazor-error-ui" style="display: none; position: fixed; bottom: 0; left: 0; right: 0; padding: 1rem; background: #ef4444; color: white; text-align: center;">
<div id="blazor-error-ui" style="display: none; position: fixed; bottom: 0; left: 0; right: 0; padding: 1rem; background: #f38ba8; color: #1e1e2e; text-align: center;">
An unhandled error has occurred. / Đã xảy ra lỗi.
<a href="." class="reload" style="color: white; text-decoration: underline; margin-left: 1rem;">Reload / Tải lại</a>
<a href="." class="reload" style="color: #1e1e2e; text-decoration: underline; margin-left: 1rem;">Reload / Tải lại</a>
<span class="dismiss" style="cursor: pointer; margin-left: 1rem;"></span>
</div>
<!-- EN: Blazor WebAssembly -->
<script src="_framework/blazor.webassembly.js"></script>
<!-- EN: MudBlazor JavaScript -->
<!-- VI: JavaScript MudBlazor -->
<script src="_content/MudBlazor/MudBlazor.min.js"></script>
</body>
</html>