Merge: auth workflow fixes into master
Co-authored-by: Velik <hongochai10@users.noreply.github.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
@inherits LayoutComponentBase
|
||||
@inherits LayoutComponentBase
|
||||
@inject IStringLocalizer<MainLayout> L
|
||||
|
||||
<MudThemeProvider IsDarkMode="true" Theme="_theme" />
|
||||
@@ -21,8 +21,8 @@
|
||||
<!-- Language Switcher -->
|
||||
<LanguageSwitcher />
|
||||
|
||||
<a href="/login" class="tpos-nav-link">@L["Nav_Login"]</a>
|
||||
<a href="#" class="btn-accent">@L["Nav_FreeTrial"]</a>
|
||||
<a href="/auth/login" class="tpos-nav-link">@L["Nav_Login"]</a>
|
||||
<a href="/register" class="btn-accent">@L["Nav_FreeTrial"]</a>
|
||||
</div>
|
||||
|
||||
<!-- Mobile hamburger button -->
|
||||
@@ -44,10 +44,10 @@
|
||||
<a href="#features" class="tpos-mobile-link" @onclick="CloseMobileMenu">@L["Nav_Features"]</a>
|
||||
<a href="#pricing" class="tpos-mobile-link" @onclick="CloseMobileMenu">@L["Nav_Pricing"]</a>
|
||||
<a href="#" class="tpos-mobile-link" @onclick="CloseMobileMenu">@L["Nav_Contact"]</a>
|
||||
<a href="/login" class="tpos-mobile-link" @onclick="CloseMobileMenu">@L["Nav_Login"]</a>
|
||||
<a href="/auth/login" class="tpos-mobile-link" @onclick="CloseMobileMenu">@L["Nav_Login"]</a>
|
||||
<div class="tpos-mobile-actions">
|
||||
<LanguageSwitcher />
|
||||
<a href="#" class="btn-accent btn-accent-lg" style="width:100%; text-align:center;">@L["Nav_FreeTrial"]</a>
|
||||
<a href="/register" class="btn-accent btn-accent-lg" style="width:100%; text-align:center;">@L["Nav_FreeTrial"]</a>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
@page "/admin"
|
||||
@layout AdminLayout
|
||||
@inherits AdminBase
|
||||
@inject PosDataService DataService
|
||||
@using WebClientTpos.Client.Services
|
||||
|
||||
@*
|
||||
EN: Admin Dashboard — overview of business metrics, stores, alerts, and recent activity.
|
||||
@@ -107,123 +109,47 @@
|
||||
<i data-lucide="store" style="color:var(--admin-orange-primary);"></i>
|
||||
Cửa hàng của bạn
|
||||
</h3>
|
||||
<a href="/admin/stores" class="admin-panel__action">Quản lý tất cả →</a>
|
||||
@if (_shops.Count > 0)
|
||||
{
|
||||
<a href="/admin/stores" class="admin-panel__action">Quản lý tất cả →</a>
|
||||
}
|
||||
</div>
|
||||
<div class="admin-panel__body" style="display:flex;flex-direction:column;gap:12px;">
|
||||
@* Store 1: Coffee House Q1 *@
|
||||
<div class="admin-store-card">
|
||||
<div class="admin-store-card__top">
|
||||
<div class="admin-store-card__info">
|
||||
<div class="admin-store-card__avatar" style="background-color:rgba(255,92,0,0.125);">
|
||||
<i data-lucide="coffee" style="color:var(--admin-orange-primary);"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="admin-store-card__name">Coffee House Q1</div>
|
||||
<div class="admin-store-card__type">Café • 123 Nguyễn Huệ, Q1</div>
|
||||
@if (_shops.Count == 0)
|
||||
{
|
||||
<div style="text-align:center;padding:40px 20px;">
|
||||
<i data-lucide="store" style="width:48px;height:48px;color:var(--admin-orange-primary);margin-bottom:16px;"></i>
|
||||
<h3 style="font-size:18px;font-weight:700;color:var(--pos-text-primary, #FFFFFF);margin:0 0 8px;">Welcome! Tạo cửa hàng đầu tiên</h3>
|
||||
<p style="font-size:14px;color:var(--pos-text-tertiary, #ADADB0);margin:0 0 20px;">Bắt đầu bằng việc tạo cửa hàng để quản lý kinh doanh của bạn.</p>
|
||||
<a href="/admin/onboarding/store" class="admin-btn-primary" style="display:inline-flex;align-items:center;gap:8px;">
|
||||
<i data-lucide="plus" style="width:16px;height:16px;"></i>
|
||||
Tạo cửa hàng ngay
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
@foreach (var shop in _shops)
|
||||
{
|
||||
<div class="admin-store-card">
|
||||
<div class="admin-store-card__top">
|
||||
<div class="admin-store-card__info">
|
||||
<div class="admin-store-card__avatar" style="background-color:rgba(255,92,0,0.125);">
|
||||
<i data-lucide="@GetShopIcon(shop.Category)" style="color:var(--admin-orange-primary);"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="admin-store-card__name">@shop.Name</div>
|
||||
<div class="admin-store-card__type">@(shop.Category ?? "Shop") • @(shop.Description ?? shop.Slug)</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="admin-status-badge admin-status-badge--@(shop.Status == "active" ? "online" : "setup")">
|
||||
<span class="admin-status-badge__dot"></span>
|
||||
@(shop.Status == "active" ? "Đang mở" : "Thiết lập")
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="admin-status-badge admin-status-badge--online">
|
||||
<span class="admin-status-badge__dot"></span>
|
||||
Đang mở
|
||||
</div>
|
||||
</div>
|
||||
<div class="admin-store-card__stats">
|
||||
<div class="admin-store-stat">
|
||||
<div class="admin-store-stat__value">45.2M</div>
|
||||
<div class="admin-store-stat__label">Doanh thu</div>
|
||||
</div>
|
||||
<div class="admin-store-stat">
|
||||
<div class="admin-store-stat__value">342</div>
|
||||
<div class="admin-store-stat__label">Đơn hàng</div>
|
||||
</div>
|
||||
<div class="admin-store-stat">
|
||||
<div class="admin-store-stat__value">5</div>
|
||||
<div class="admin-store-stat__label">Nhân viên</div>
|
||||
</div>
|
||||
<div class="admin-store-stat">
|
||||
<div class="admin-store-stat__value">48</div>
|
||||
<div class="admin-store-stat__label">Sản phẩm</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@* Store 2: Nhà hàng Q3 *@
|
||||
<div class="admin-store-card">
|
||||
<div class="admin-store-card__top">
|
||||
<div class="admin-store-card__info">
|
||||
<div class="admin-store-card__avatar" style="background-color:rgba(59,130,246,0.125);">
|
||||
<i data-lucide="utensils" style="color:#3B82F6;"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="admin-store-card__name">Nhà hàng Q3</div>
|
||||
<div class="admin-store-card__type">Restaurant • 456 Lê Văn Sỹ, Q3</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="admin-status-badge admin-status-badge--online">
|
||||
<span class="admin-status-badge__dot"></span>
|
||||
Đang mở
|
||||
</div>
|
||||
</div>
|
||||
<div class="admin-store-card__stats">
|
||||
<div class="admin-store-stat">
|
||||
<div class="admin-store-stat__value">62.8M</div>
|
||||
<div class="admin-store-stat__label">Doanh thu</div>
|
||||
</div>
|
||||
<div class="admin-store-stat">
|
||||
<div class="admin-store-stat__value">185</div>
|
||||
<div class="admin-store-stat__label">Đơn hàng</div>
|
||||
</div>
|
||||
<div class="admin-store-stat">
|
||||
<div class="admin-store-stat__value">8</div>
|
||||
<div class="admin-store-stat__label">Nhân viên</div>
|
||||
</div>
|
||||
<div class="admin-store-stat">
|
||||
<div class="admin-store-stat__value">72</div>
|
||||
<div class="admin-store-stat__label">Sản phẩm</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@* Store 3: Karaoke Star Q7 *@
|
||||
<div class="admin-store-card">
|
||||
<div class="admin-store-card__top">
|
||||
<div class="admin-store-card__info">
|
||||
<div class="admin-store-card__avatar" style="background-color:rgba(139,92,246,0.125);">
|
||||
<i data-lucide="mic" style="color:#8B5CF6;"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="admin-store-card__name">Karaoke Star Q7</div>
|
||||
<div class="admin-store-card__type">Karaoke • 789 Nguyễn Thị Thập, Q7</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="admin-status-badge admin-status-badge--setup">
|
||||
<span class="admin-status-badge__dot"></span>
|
||||
Thiết lập
|
||||
</div>
|
||||
</div>
|
||||
<div class="admin-store-card__stats">
|
||||
<div class="admin-store-stat">
|
||||
<div class="admin-store-stat__value">--</div>
|
||||
<div class="admin-store-stat__label">Doanh thu</div>
|
||||
</div>
|
||||
<div class="admin-store-stat">
|
||||
<div class="admin-store-stat__value">--</div>
|
||||
<div class="admin-store-stat__label">Đơn hàng</div>
|
||||
</div>
|
||||
<div class="admin-store-stat">
|
||||
<div class="admin-store-stat__value">0</div>
|
||||
<div class="admin-store-stat__label">Nhân viên</div>
|
||||
</div>
|
||||
<div class="admin-store-stat">
|
||||
<div class="admin-store-stat__value">0</div>
|
||||
<div class="admin-store-stat__label">Sản phẩm</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="admin-store-card__cta admin-store-card__cta--warning">
|
||||
<i data-lucide="settings" style="width:14px;height:14px;"></i>
|
||||
Hoàn tất thiết lập
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -316,3 +242,34 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private List<PosDataService.ShopInfo> _shops = new();
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
IsLoading = true;
|
||||
try
|
||||
{
|
||||
_shops = await DataService.GetShopsAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
_shops = new();
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetShopIcon(string? category) => category?.ToLowerInvariant() switch
|
||||
{
|
||||
"cafe" or "café" or "coffee" => "coffee",
|
||||
"restaurant" or "nhà hàng" => "utensils",
|
||||
"karaoke" => "mic",
|
||||
"spa" => "sparkles",
|
||||
"retail" => "shopping-bag",
|
||||
_ => "store"
|
||||
};
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
}
|
||||
|
||||
<div class="auth-footer">
|
||||
<a href="/login" class="link-primary">@L["Auth_ForgotPassword_BackToLogin"]</a>
|
||||
<a href="/auth/login" class="link-primary">@L["Auth_ForgotPassword_BackToLogin"]</a>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
@page "/auth/login/customer"
|
||||
@page "/login"
|
||||
@layout AuthLayout
|
||||
@using WebClientTpos.Client.Components.Auth
|
||||
@inherits AuthBase
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@
|
||||
|
||||
<div class="auth-footer">
|
||||
<span>@L["Auth_Register_HaveAccount"]</span>
|
||||
<a href="/login" class="link-primary">@L["Auth_Register_LoginLink"]</a>
|
||||
<a href="/auth/login" class="link-primary">@L["Auth_Register_LoginLink"]</a>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
@@ -127,7 +127,7 @@
|
||||
// EN: Redirect to login after 2 seconds
|
||||
// VI: Chuyển hướng đến đăng nhập sau 2 giây
|
||||
await Task.Delay(2000);
|
||||
Navigation.NavigateTo("/login");
|
||||
Navigation.NavigateTo("/auth/login");
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
@message
|
||||
</div>
|
||||
<div class="mt-6">
|
||||
<a href="/login" class="link-primary">@L["Auth_VerifyEmail_GoToLogin"]</a>
|
||||
<a href="/auth/login" class="link-primary">@L["Auth_VerifyEmail_GoToLogin"]</a>
|
||||
</div>
|
||||
}
|
||||
</section>
|
||||
|
||||
@@ -20,6 +20,10 @@ builder.Services.AddSingleton(sp => new HttpClient { BaseAddress = new Uri(new U
|
||||
// VI: Thêm POS data service cho BFF API calls
|
||||
builder.Services.AddScoped<WebClientTpos.Client.Services.PosDataService>();
|
||||
|
||||
// EN: Add auth state service for role-based redirects
|
||||
// VI: Thêm auth state service cho điều hướng theo vai trò
|
||||
builder.Services.AddSingleton<WebClientTpos.Client.Services.AuthStateService>();
|
||||
|
||||
// EN: Add MudBlazor services
|
||||
// VI: Thêm các services của MudBlazor
|
||||
builder.Services.AddMudServices();
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
namespace WebClientTpos.Client.Services;
|
||||
|
||||
public class AuthStateService
|
||||
{
|
||||
public bool IsAuthenticated { get; private set; }
|
||||
public string? UserEmail { get; private set; }
|
||||
public string? UserRole { get; private set; } // "owner", "staff", "customer", "branch"
|
||||
public string? Token { get; private set; }
|
||||
|
||||
public event Action? OnChange;
|
||||
|
||||
public void Login(string email, string token, string role)
|
||||
{
|
||||
IsAuthenticated = true;
|
||||
UserEmail = email;
|
||||
Token = token;
|
||||
UserRole = role;
|
||||
OnChange?.Invoke();
|
||||
}
|
||||
|
||||
public void Logout()
|
||||
{
|
||||
IsAuthenticated = false;
|
||||
UserEmail = null;
|
||||
Token = null;
|
||||
UserRole = null;
|
||||
OnChange?.Invoke();
|
||||
}
|
||||
|
||||
public string GetPortalUrl() => UserRole switch
|
||||
{
|
||||
"owner" or "admin" => "/admin",
|
||||
"staff" => "/pos/cafe",
|
||||
"branch" => "/admin",
|
||||
"customer" => "/app",
|
||||
_ => "/auth/login"
|
||||
};
|
||||
}
|
||||
@@ -56,12 +56,11 @@ if (app.Environment.IsDevelopment())
|
||||
{
|
||||
app.MapOpenApi();
|
||||
app.UseDeveloperExceptionPage();
|
||||
app.UseWebAssemblyDebugging();
|
||||
}
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
|
||||
// EN: Enable CORS
|
||||
// VI: Kích hoạt CORS
|
||||
// EN: Enable CORS
|
||||
// VI: Kích hoạt CORS
|
||||
app.UseCors("BlazorClient");
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{"Version":1,"Id":"40845B4B36348C1B2FD55311A93F4280","Created":"2026-02-26T17:40:10.6713835Z","Algorithm":"RS256","IsX509Certificate":false,"Data":"CfDJ8OE6IQaarJdMrFb-ELKfkmJdiuzUgIT8mztIqfJcZrKHdQw5UiPZvLOdgRvR4ffdcIvMXsaKsH-l0TKza8hmQVhE5RZX2CpQtd_EceQqb4xc7CMzvtCAr6f4HVbbIYtfGOfzzvLPQ2kLhHzoKRVnhFVWKkHswkDekog_H_a6MxDt9WbddI3xuM8c97YA8lsN4hojKfHhKSTE_05eHmXAir6NtMZEgNziU6h99r9EInp7acESdlQ4aUqKapVpDzYb1MTNxxFwggbU_gNlDNwhOZi2-ElWXNJ4pNBwF1ZUjRgydNwdu-qUsYejMkhDPpkX9nXRmU0Hofea-XPDGvcMu5u6YNXB7bRHaY6G1NB1-UGkGbFhM_CQMAuUM8Sp17Zxkgu5YYC9xcWaefdi82xaGl5SGEAV0nfFGZctvqG9YAYR7U2o2hXBOgN2pQuvSdfe4d4IP1KjGa726eVzEMwfzqy-wWpO_UKINHEKEeVHYfhjWvA3zofuCis154kVThw3KFVBOuLCcguF6AqiwXV_veLU-sMaq3sgLwHduxoaNeVZbGAWsqseJC3G1KPjibrQ6Dd1du0uzLMaOUctjPzsDwU4nFpXBUs-pGZuG03vltOJh0U_ankoGW4q5XjsyFQGDFKivdcrufst2DFx_pvD1PtkAawdAoLG9pZy5lrhCxwv_uzDw3pdIK4Q4C-eOpkC-nOsZE3PWfjdA0ouZ09USbj_qeA6mpF0TIafuJTRlz-KkSiy5X4qLWiznGQWO8JmvZGNLx2P8eX_IijhzZaZXknTDRLsmFi5bq1NaEPGvJRPnS_n2E5N2YMC_RBq6ct0pppTnsJaYOip-DPLcA2WUtw_j8QaLD9Y80a0Gv1ky_hU-iivKhUFsvWxvsuJqRFcbFBw5CO-wydUxvDR4ejznen1nAKxh3Jh9b1l26LfMllns9FBcMrTzYYHMkaZFSMXsY-KhaBkcw-46O8aunZlzioxP5oTsaAyI_Ru3jlCJLteCtXoP8c8gpnOsem1praCx6i8n418hmrw1HpK3gdNifrE4fW_M2h5UxzZLzzRZkhvsv_ZgKorpvYH5QrIGzOWUR2DF_wCCTcZbU6PFy0ztTgw_O8NGHi3kAveu_Dff566oWj0oAfNQ0g0lPXnwkK7ZxITHwL47iVhDvEt5OvSR6zTnX4J2J2P3MajQYPkojT3zk5yezEYVp0_hHjQ155f44Q-MoTVaDxJhvF3nN0JXCZ4Xr2wcGiFwGa8OPF0t1H66dapUNwYMfQdPQx8_KiOvSHQUrLtPh-fknasU-dD54TAZotts4zHKRw05W6_7kNaM7mMSCjUd8y1ub2Ae5t0qt1Vt123df-SyBI1vVKFtMPkCpYtTYzgtoR_HEMLUF3bqfoJqXoHvJDBM86DitJYYz-Zcdiukx5rMHSfz6XNQx1m9T8twbXwrowd5K7G3kaDZkXHKtv-Shbsb99gkyq2uHSPDELafyOJfrFQLcoapDuaXCHss8nIhFFZs_YTqs_eGzyUW_tu2fnech-3IO7dlaHmShXJhkE76oEW7eeJCT9WwSbsiSosdU38bCRZeJ05ATIgIdcRGhXF8zeOwEDK-TZvltb0x5JgzQr4BXlPAfi7cQuyyNVLIXz9tyoC0WuFnSS1C1-oJb0ZiW46HpafPzU7eh3is7zwBqv7UkiIZtPUHapJEvxSNNeGM95Mu7h5B1GeoYgurtYfNo4HBkmxsRqsbnEkGuLkfaK33k3ywZZcJx7-BqR_dtzOtdiX0vXcvQr-MnkpleCOKxUr2q0NMjRqOYHAGEdXhWHAqcDhIR5hpx6nFMnCFo2GJu-BpHAJSPQU9OuEWhOgnQmJXM_0NyuaXWQ5xSNGZExKr814ILSTopc56AYHU-AmZQ_Pt2paDcBDj7c3If9N2yo8H_8lxXO1hPBxS7IbJMfMGFDdJrvuHczF1xVzo1x6sYQbRNkezBpIglJXr0GSqwyJ1Ca6BYuCG3_G-jS-MTMkNYiIihZt7OhKtriwZ5YG582R9yyWRfw6YTv_7W-IL1lxdSGFNajXnrtGl513TvQmadqQA1lwjSUtCQYE9b-zXYaFwyn1JdOuJ4fMY2rGdlhODVPot2VGxqVVcjY4c_SN3WHzcxSCcRL-L49oN65OboVMFQ7slH4Gyx4J6gZBMsUFWl_iG5Y55OcfnOgEwbXzjl8yYy69z9NbzrElZ-1h5DlT2QETqWJb1Ujxhwpge-W5RIMxVptNRcBCKVpEhs4VqpKTtUX0v-7srkviEECgfpRAQrCqRZr7CDE6JS7bvOvYdrS0v39z86WhWsxrE7fkxMaoH0mzudU3g9yaD0f5HLjvHmA8VSRugAWi0OQqK7bVysc_q0wFBj6-MQLPD-CBW98X1FMsgVpBNIGeij5He9-iRt3Q9JZbMZakIB2BfnSrkvS9gJ68gfxX_rVhLJs8eF78Ftg1jXdNPZFXOQNqloVym4Oj","DataProtected":true}
|
||||
Reference in New Issue
Block a user