feat: Integrate MudBlazor component library and refactor the base UI layout and home page.
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user