refactor(web-client-tpos): centralize theme, fix layout bugs, cleanup dead code
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
// EN: Centralized MudBlazor theme definitions for all layouts.
|
||||
// VI: Định nghĩa MudBlazor theme tập trung cho tất cả layouts.
|
||||
|
||||
using MudBlazor;
|
||||
|
||||
namespace WebClientTpos.Client;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Centralized theme configuration — used by all layouts (Admin, Auth, POS, Marketing).
|
||||
/// VI: Cấu hình theme tập trung — sử dụng bởi tất cả layouts (Admin, Auth, POS, Marketing).
|
||||
/// </summary>
|
||||
public static class AppTheme
|
||||
{
|
||||
/// <summary>
|
||||
/// EN: Default dark theme — aPOS brand orange (#FF5C00) on dark background.
|
||||
/// VI: Theme tối mặc định — thương hiệu aPOS cam (#FF5C00) trên nền tối.
|
||||
/// Used by: AdminLayout, AuthLayout, PosLayout
|
||||
/// </summary>
|
||||
public static MudTheme DefaultDark { get; } = new()
|
||||
{
|
||||
PaletteDark = new PaletteDark()
|
||||
{
|
||||
Primary = "#FF5C00",
|
||||
PrimaryContrastText = "#FFFFFF",
|
||||
AppbarBackground = "#1A1A1D",
|
||||
AppbarText = "#FFFFFF",
|
||||
Background = "#0A0A0B",
|
||||
Surface = "#1A1A1D",
|
||||
TextPrimary = "#FFFFFF",
|
||||
TextSecondary = "#ADADB0",
|
||||
ActionDefault = "#FFFFFF",
|
||||
LinesDefault = "#1F1F23"
|
||||
}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// EN: Marketing dark theme — yellow accent (#FACC15) for tMarketing module.
|
||||
/// VI: Theme tối Marketing — điểm nhấn vàng (#FACC15) cho module tMarketing.
|
||||
/// Used by: MarketingLayout
|
||||
/// </summary>
|
||||
public static MudTheme MarketingDark { get; } = new()
|
||||
{
|
||||
PaletteDark = new PaletteDark()
|
||||
{
|
||||
Primary = "#FACC15",
|
||||
PrimaryContrastText = "#18181B",
|
||||
AppbarBackground = "#0F0F10",
|
||||
AppbarText = "#FAFAFA",
|
||||
Background = "#18181B",
|
||||
Surface = "#0F0F10",
|
||||
TextPrimary = "#FAFAFA",
|
||||
TextSecondary = "#71717A",
|
||||
ActionDefault = "#FAFAFA",
|
||||
LinesDefault = "#27272A"
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -14,7 +14,7 @@
|
||||
@inject Microsoft.Extensions.Localization.IStringLocalizer<AdminLayout> L
|
||||
@using WebClientTpos.Client.Services
|
||||
|
||||
<MudThemeProvider IsDarkMode="true" Theme="_theme" />
|
||||
<MudThemeProvider IsDarkMode="true" Theme="AppTheme.DefaultDark" />
|
||||
<MudPopoverProvider />
|
||||
<MudDialogProvider />
|
||||
<MudSnackbarProvider />
|
||||
@@ -135,6 +135,13 @@
|
||||
|
||||
@* ═══ MAIN AREA ═══ *@
|
||||
<main class="admin-main">
|
||||
@* Mobile-only hamburger toggle *@
|
||||
<div class="admin-mobile-bar">
|
||||
<button class="admin-mobile-toggle" @onclick="ToggleSidebar">
|
||||
<i data-lucide="menu"></i>
|
||||
</button>
|
||||
<span class="admin-mobile-bar__title">GoodGo Admin</span>
|
||||
</div>
|
||||
<ErrorBoundary @ref="_errorBoundary">
|
||||
<ChildContent>
|
||||
<CascadingValue Value="this">
|
||||
@@ -263,22 +270,6 @@
|
||||
NavigationManager.NavigateTo("/auth/login", forceLoad: true);
|
||||
}
|
||||
|
||||
private MudTheme _theme = new()
|
||||
{
|
||||
PaletteDark = new PaletteDark()
|
||||
{
|
||||
Primary = "#FF5C00",
|
||||
PrimaryContrastText = "#FFFFFF",
|
||||
AppbarBackground = "#1A1A1D",
|
||||
AppbarText = "#FFFFFF",
|
||||
Background = "#0A0A0B",
|
||||
Surface = "#1A1A1D",
|
||||
TextPrimary = "#FFFFFF",
|
||||
TextSecondary = "#ADADB0",
|
||||
ActionDefault = "#FFFFFF",
|
||||
LinesDefault = "#1F1F23"
|
||||
}
|
||||
};
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
@inherits LayoutComponentBase
|
||||
@inject IJSRuntime JS
|
||||
@inject Microsoft.Extensions.Localization.IStringLocalizer<AuthLayout> L
|
||||
|
||||
<MudThemeProvider IsDarkMode="true" Theme="_theme" />
|
||||
<MudThemeProvider IsDarkMode="true" Theme="AppTheme.DefaultDark" />
|
||||
<MudPopoverProvider />
|
||||
<MudDialogProvider />
|
||||
<MudSnackbarProvider />
|
||||
@@ -10,10 +11,10 @@
|
||||
<div class="tpos-navbar-inner">
|
||||
<a href="/" class="tpos-logo">aPOS</a>
|
||||
<div class="tpos-nav-links">
|
||||
<a href="/#features" class="tpos-nav-link">Tính năng</a>
|
||||
<a href="/#pricing" class="tpos-nav-link">Bảng giá</a>
|
||||
<a href="/login" class="tpos-nav-link">Đăng nhập</a>
|
||||
<a href="/register" class="btn-accent">Dùng thử</a>
|
||||
<a href="/#features" class="tpos-nav-link">@L["Nav_Features"]</a>
|
||||
<a href="/#pricing" class="tpos-nav-link">@L["Nav_Pricing"]</a>
|
||||
<a href="/auth/login" class="tpos-nav-link">@L["Nav_Login"]</a>
|
||||
<a href="/auth/register" class="btn-accent">@L["Nav_FreeTrial"]</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
@@ -29,21 +30,4 @@
|
||||
{
|
||||
try { await JS.InvokeVoidAsync("lucide.createIcons"); } catch { }
|
||||
}
|
||||
|
||||
private MudTheme _theme = new()
|
||||
{
|
||||
PaletteDark = new PaletteDark()
|
||||
{
|
||||
Primary = "#FF5C00",
|
||||
PrimaryContrastText = "#FFFFFF",
|
||||
AppbarBackground = "rgba(10,10,11,0.85)",
|
||||
AppbarText = "#FFFFFF",
|
||||
Background = "#0A0A0B",
|
||||
Surface = "#111113",
|
||||
TextPrimary = "#FFFFFF",
|
||||
TextSecondary = "#ADADB0",
|
||||
ActionDefault = "#FFFFFF",
|
||||
LinesDefault = "#1F1F23"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -6,8 +6,10 @@
|
||||
@inherits LayoutComponentBase
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject IJSRuntime JS
|
||||
@inject WebClientTpos.Client.Services.AuthService AuthSvc
|
||||
@using WebClientTpos.Client.Services
|
||||
|
||||
<MudThemeProvider IsDarkMode="true" Theme="_theme" />
|
||||
<MudThemeProvider IsDarkMode="true" Theme="AppTheme.MarketingDark" />
|
||||
<MudPopoverProvider />
|
||||
<MudDialogProvider />
|
||||
<MudSnackbarProvider />
|
||||
@@ -125,22 +127,12 @@
|
||||
private void ToggleSidebar() => _sidebarOpen = !_sidebarOpen;
|
||||
private void CloseSidebar() => _sidebarOpen = false;
|
||||
private void ToggleSearch() => _searchOpen = !_searchOpen;
|
||||
private void Logout() => NavigationManager.NavigateTo("/login");
|
||||
|
||||
private MudTheme _theme = new()
|
||||
// EN: Properly clear auth state on logout instead of just navigating
|
||||
// VI: Xóa auth state đúng cách khi đăng xuất thay vì chỉ điều hướng
|
||||
private async Task Logout()
|
||||
{
|
||||
PaletteDark = new PaletteDark()
|
||||
{
|
||||
Primary = "#FACC15",
|
||||
PrimaryContrastText = "#18181B",
|
||||
AppbarBackground = "#0F0F10",
|
||||
AppbarText = "#FAFAFA",
|
||||
Background = "#18181B",
|
||||
Surface = "#0F0F10",
|
||||
TextPrimary = "#FAFAFA",
|
||||
TextSecondary = "#71717A",
|
||||
ActionDefault = "#FAFAFA",
|
||||
LinesDefault = "#27272A"
|
||||
}
|
||||
};
|
||||
await AuthSvc.LogoutAsync();
|
||||
NavigationManager.NavigateTo("/auth/login", forceLoad: true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject WebClientTpos.Client.Services.PosDataService DataService
|
||||
|
||||
<MudThemeProvider IsDarkMode="true" Theme="_theme" />
|
||||
<MudThemeProvider IsDarkMode="true" Theme="AppTheme.DefaultDark" />
|
||||
<MudPopoverProvider />
|
||||
<MudDialogProvider />
|
||||
<MudSnackbarProvider />
|
||||
@@ -65,21 +65,6 @@
|
||||
|
||||
private void GoToAdmin() => NavigationManager.NavigateTo("/admin");
|
||||
|
||||
private MudTheme _theme = new()
|
||||
{
|
||||
PaletteDark = new PaletteDark()
|
||||
{
|
||||
Primary = "#FF5C00",
|
||||
PrimaryContrastText = "#FFFFFF",
|
||||
AppbarBackground = "#1A1A1D",
|
||||
AppbarText = "#FFFFFF",
|
||||
Background = "#0A0A0B",
|
||||
Surface = "#1A1A1D",
|
||||
TextPrimary = "#FFFFFF",
|
||||
TextSecondary = "#ADADB0",
|
||||
ActionDefault = "#FFFFFF",
|
||||
LinesDefault = "#1F1F23"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,136 +0,0 @@
|
||||
@page "/login"
|
||||
@using WebClientTpos.Shared.DTOs
|
||||
@using WebClientTpos.Shared
|
||||
@inject HttpClient Http
|
||||
@inject NavigationManager Navigation
|
||||
@inject IStringLocalizer<Login> L
|
||||
|
||||
@*
|
||||
EN: Login page with email/password authentication.
|
||||
VI: Trang đăng nhập với xác thực email/mật khẩu.
|
||||
*@
|
||||
|
||||
<PageTitle>@L["Auth_Login_Title"]</PageTitle>
|
||||
|
||||
<div class="auth-container">
|
||||
<section class="auth-card">
|
||||
<h1 class="auth-title">@L["Auth_Login_Title"]</h1>
|
||||
<p class="auth-subtitle">@L["Auth_Login_Subtitle"]</p>
|
||||
|
||||
<EditForm Model="@loginModel" OnValidSubmit="HandleLogin" FormName="LoginForm">
|
||||
<DataAnnotationsValidator />
|
||||
|
||||
<div class="form-group">
|
||||
<label for="login-email">@L["Auth_Login_Email"] *</label>
|
||||
<InputText id="login-email"
|
||||
@bind-Value="loginModel.Email"
|
||||
class="form-input"
|
||||
placeholder="email@example.com"
|
||||
autocomplete="email" />
|
||||
<ValidationMessage For="() => loginModel.Email" class="validation-message" />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="login-password">@L["Auth_Login_Password"] *</label>
|
||||
<InputText id="login-password"
|
||||
@bind-Value="loginModel.Password"
|
||||
type="password"
|
||||
class="form-input"
|
||||
placeholder="••••••••"
|
||||
autocomplete="current-password" />
|
||||
<ValidationMessage For="() => loginModel.Password" class="validation-message" />
|
||||
</div>
|
||||
|
||||
<div class="form-actions-row">
|
||||
<div class="checkbox-group">
|
||||
<InputCheckbox id="remember-me"
|
||||
@bind-Value="loginModel.RememberMe"
|
||||
class="form-checkbox" />
|
||||
<label for="remember-me" class="checkbox-label">@L["Auth_Login_RememberMe"]</label>
|
||||
</div>
|
||||
|
||||
<a href="/forgot-password" class="link-secondary">@L["Auth_Login_ForgotPassword"]</a>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn-primary btn-full" disabled="@isSubmitting">
|
||||
@if (isSubmitting)
|
||||
{
|
||||
<span class="spinner-small"></span>
|
||||
<span>@L["Common_Loading"]</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
@L["Auth_Login_Submit"]
|
||||
}
|
||||
</button>
|
||||
</EditForm>
|
||||
|
||||
@if (!string.IsNullOrEmpty(message))
|
||||
{
|
||||
<div class="alert @(success ? "alert-success" : "alert-error")">
|
||||
@message
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="auth-footer">
|
||||
<span>@L["Auth_Login_NoAccount"]</span>
|
||||
<a href="/register" class="link-primary">@L["Auth_Login_RegisterLink"]</a>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private LoginDto loginModel = new();
|
||||
private bool isSubmitting = false;
|
||||
private string message = "";
|
||||
private bool success = false;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Handle login form submission.
|
||||
/// VI: Xử lý submit form đăng nhập.
|
||||
/// </summary>
|
||||
private async Task HandleLogin()
|
||||
{
|
||||
isSubmitting = true;
|
||||
message = "";
|
||||
|
||||
try
|
||||
{
|
||||
var response = await Http.PostAsJsonAsync("api/auth/login", loginModel);
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
var result = await response.Content.ReadFromJsonAsync<ApiResponse<UserProfileDto>>();
|
||||
if (result?.Success == true && result.Data != null)
|
||||
{
|
||||
success = true;
|
||||
message = string.Format(L["Auth_Login_Success"], result.Data.DisplayName);
|
||||
|
||||
// EN: Redirect to home after 1 second
|
||||
// VI: Chuyển hướng về trang chủ sau 1 giây
|
||||
await Task.Delay(1000);
|
||||
Navigation.NavigateTo("/");
|
||||
}
|
||||
else
|
||||
{
|
||||
success = false;
|
||||
message = L["Auth_Login_Error"];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
success = false;
|
||||
message = L["Auth_Login_Error"];
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
success = false;
|
||||
message = $"{L["Common_Error"]}: {ex.Message}";
|
||||
}
|
||||
finally
|
||||
{
|
||||
isSubmitting = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1518,4 +1518,53 @@ select.admin-form-input {
|
||||
|
||||
.admin-status-badge--offline .admin-status-badge__dot {
|
||||
background-color: var(--admin-danger);
|
||||
}
|
||||
|
||||
/* ═════════════════════════════════════════════════════════════════════════
|
||||
17. MOBILE BAR (hamburger toggle header — visible ≤ 1024px only)
|
||||
═════════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
/* EN: Hidden on desktop / VI: Ẩn trên desktop */
|
||||
.admin-mobile-bar {
|
||||
display: none;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 20px;
|
||||
background-color: var(--admin-bg-elevated);
|
||||
border-bottom: 1px solid var(--admin-border-subtle);
|
||||
}
|
||||
|
||||
.admin-mobile-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: var(--admin-radius-md);
|
||||
background: transparent;
|
||||
border: 1px solid var(--admin-border-default);
|
||||
color: var(--admin-text-primary);
|
||||
cursor: pointer;
|
||||
transition: background 0.2s ease;
|
||||
}
|
||||
|
||||
.admin-mobile-toggle:hover {
|
||||
background-color: var(--admin-bg-interactive);
|
||||
}
|
||||
|
||||
.admin-mobile-toggle i {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.admin-mobile-bar__title {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: var(--admin-text-primary);
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.admin-mobile-bar {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user