diff --git a/apps/web-client-tpos-net/src/WebClientTpos.Client/Components/LanguageSwitcher.razor b/apps/web-client-tpos-net/src/WebClientTpos.Client/Components/LanguageSwitcher.razor index 1325ee47..b35a37a7 100644 --- a/apps/web-client-tpos-net/src/WebClientTpos.Client/Components/LanguageSwitcher.razor +++ b/apps/web-client-tpos-net/src/WebClientTpos.Client/Components/LanguageSwitcher.razor @@ -1,5 +1,6 @@ @using System.Globalization @inject NavigationManager Navigation +@inject IJSRuntime JS @@ -29,39 +30,37 @@ @code { private string GetCurrentLabel() { - var uri = new Uri(Navigation.Uri); - var path = uri.PathAndQuery; - - // Simple heuristic: if path starts with /vi-VN or /vi, show VI. Default EN. - if (path.StartsWith("/vi", StringComparison.OrdinalIgnoreCase)) - { - return "VI"; - } - return "EN"; + var culture = CultureInfo.CurrentUICulture.Name; + return culture.StartsWith("vi", StringComparison.OrdinalIgnoreCase) ? "VI" : "EN"; } - private void SwitchLanguage(string targetCulture) + private async Task SwitchLanguage(string targetCulture) { - var uri = new Uri(Navigation.Uri); - var path = uri.PathAndQuery; + // Save to localStorage for persistence across page reloads + await JS.InvokeVoidAsync("localStorage.setItem", "aPOS_culture", targetCulture); + // Force full page reload to reinitialize the WASM app with new culture + var currentUri = Navigation.Uri; + var uri = new Uri(currentUri); + + // Build clean URL (strip any culture segments from path) + var path = uri.AbsolutePath; var segments = path.Split('/', StringSplitOptions.RemoveEmptyEntries); - string newPath; - if (segments.Length > 0 && (segments[0].Equals("vi-VN", StringComparison.OrdinalIgnoreCase) || - segments[0].Equals("en-US", StringComparison.OrdinalIgnoreCase) || - segments[0].Equals("vi", StringComparison.OrdinalIgnoreCase) || - segments[0].Equals("en", StringComparison.OrdinalIgnoreCase))) + // Remove existing culture segment if present + if (segments.Length > 0 && ( + segments[0].Equals("vi-VN", StringComparison.OrdinalIgnoreCase) || + segments[0].Equals("en-US", StringComparison.OrdinalIgnoreCase) || + segments[0].Equals("vi", StringComparison.OrdinalIgnoreCase) || + segments[0].Equals("en", StringComparison.OrdinalIgnoreCase))) { - segments[0] = targetCulture; - newPath = "/" + string.Join('/', segments); + segments = segments.Skip(1).ToArray(); } - else - { - if (path == "/") path = ""; - newPath = $"/{targetCulture}{path}"; - } - - Navigation.NavigateTo(newPath, forceLoad: true); + + var cleanPath = "/" + string.Join('/', segments); + if (cleanPath == "/") cleanPath = ""; + + // Navigate to root with forceLoad to reinitialize WASM + Navigation.NavigateTo($"{cleanPath}", forceLoad: true); } } diff --git a/apps/web-client-tpos-net/src/WebClientTpos.Client/Localization/LocalizationCache.cs b/apps/web-client-tpos-net/src/WebClientTpos.Client/Localization/LocalizationCache.cs index 98f64201..44aaefbc 100644 --- a/apps/web-client-tpos-net/src/WebClientTpos.Client/Localization/LocalizationCache.cs +++ b/apps/web-client-tpos-net/src/WebClientTpos.Client/Localization/LocalizationCache.cs @@ -7,7 +7,7 @@ public class LocalizationCache { private readonly HttpClient _httpClient; private Dictionary _strings = new(); - private bool _isLoaded; + private string _loadedCulture = ""; public LocalizationCache(HttpClient httpClient) { @@ -25,21 +25,22 @@ public class LocalizationCache public async Task LoadAsync(CultureInfo culture) { - if (_isLoaded) return; // Or check if culture changed + var cultureName = culture.Name; + + // Fallback for simple culture codes + if (cultureName == "vi") cultureName = "vi-VN"; + if (cultureName == "en") cultureName = "en-US"; + + // Skip reload only if same culture is already loaded + if (_loadedCulture == cultureName) return; try { - var cultureName = culture.Name; - // Map generic "vi" to "vi-VN" if needed, but for now we trust the culture name matches file - // Fallback for simple "vi" -> "vi-VN" - if (cultureName == "vi") cultureName = "vi-VN"; - if (cultureName == "en") cultureName = "en-US"; - var loaded = await _httpClient.GetFromJsonAsync>($"/locales/{cultureName}.json?v={DateTime.Now.Ticks}"); if (loaded != null) { _strings = loaded; - _isLoaded = true; + _loadedCulture = cultureName; } } catch (Exception ex) diff --git a/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Home.razor b/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Home.razor index fe6e2d07..42db4f16 100644 --- a/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Home.razor +++ b/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Home.razor @@ -1,4 +1,5 @@ @page "/" +@page "/{culture}" @inject IStringLocalizer L @inject IJSRuntime JS @@ -33,7 +34,7 @@
- @L["HeroMockup_Alt"] + @L[
@@ -93,13 +94,18 @@ @foreach (var ind in _industries) {
-

@L[ind.Title]

-

@L[ind.Desc]

-
- @foreach (var chip in L[ind.Chips].Value.Split(',')) - { - ✨ @chip.Trim() - } +
+ @L[ind.Title] +
+
+

@L[ind.Title]

+

@L[ind.Desc]

+
+ @foreach (var chip in L[ind.Chips].Value.Split(',')) + { + ✦ @chip.Trim() + } +
} @@ -159,9 +165,9 @@

@L["Plan_Starter_Desc"]


    -
  • @L["Plan_Starter_Feature1"]
  • -
  • @L["Plan_Starter_Feature2"]
  • -
  • @L["Plan_Starter_Feature3"]
  • +
  • @L["Plan_Starter_Feature1"]
  • +
  • @L["Plan_Starter_Feature2"]
  • +
  • @L["Plan_Starter_Feature3"]
@L["Plan_Starter_CTA"]
@@ -177,9 +183,9 @@

@L["Plan_Pro_Desc"]


    -
  • @L["Plan_Pro_Feature1"]
  • -
  • @L["Plan_Pro_Feature2"]
  • -
  • @L["Plan_Pro_Feature3"]
  • +
  • @L["Plan_Pro_Feature1"]
  • +
  • @L["Plan_Pro_Feature2"]
  • +
  • @L["Plan_Pro_Feature3"]
@L["Plan_Pro_CTA"] @@ -194,9 +200,9 @@

@L["Plan_Enterprise_Desc"]


    -
  • @L["Plan_Enterprise_Feature1"]
  • -
  • @L["Plan_Enterprise_Feature2"]
  • -
  • @L["Plan_Enterprise_Feature3"]
  • +
  • @L["Plan_Enterprise_Feature1"]
  • +
  • @L["Plan_Enterprise_Feature2"]
  • +
  • @L["Plan_Enterprise_Feature3"]
@L["Plan_Enterprise_CTA"] @@ -287,6 +293,8 @@ @code { + [Parameter] public string? culture { get; set; } + // Initialize Lucide icons after render protected override async Task OnAfterRenderAsync(bool firstRender) { @@ -309,14 +317,14 @@ ]; // Industry cards data (5 industries matching Pencil design) - private record IndustryItem(string Title, string Desc, string Chips); + private record IndustryItem(string Title, string Desc, string Chips, string Image); private readonly IndustryItem[] _industries = [ - new("Industry_Restaurant_Title", "Industry_Restaurant_Desc", "Industry_Restaurant_Chips"), - new("Industry_Bar_Title", "Industry_Bar_Desc", "Industry_Bar_Chips"), - new("Industry_Karaoke_Title", "Industry_Karaoke_Desc", "Industry_Karaoke_Chips"), - new("Industry_Coffee_Title", "Industry_Coffee_Desc", "Industry_Coffee_Chips"), - new("Industry_Spa_Title", "Industry_Spa_Desc", "Industry_Spa_Chips"), + new("Industry_Restaurant_Title", "Industry_Restaurant_Desc", "Industry_Restaurant_Chips", "/images/home/fnb-ai.png"), + new("Industry_Bar_Title", "Industry_Bar_Desc", "Industry_Bar_Chips", "/images/home/bar-ai.png"), + new("Industry_Karaoke_Title", "Industry_Karaoke_Desc", "Industry_Karaoke_Chips", "/images/home/karaoke-ai.png"), + new("Industry_Coffee_Title", "Industry_Coffee_Desc", "Industry_Coffee_Chips", "/images/home/coffee-ai.png"), + new("Industry_Spa_Title", "Industry_Spa_Desc", "Industry_Spa_Chips", "/images/home/spa-ai.png"), ]; // Add-on items with icons (matching Pencil design) diff --git a/apps/web-client-tpos-net/src/WebClientTpos.Client/Program.cs b/apps/web-client-tpos-net/src/WebClientTpos.Client/Program.cs index 264318ac..17c6ebb2 100644 --- a/apps/web-client-tpos-net/src/WebClientTpos.Client/Program.cs +++ b/apps/web-client-tpos-net/src/WebClientTpos.Client/Program.cs @@ -5,6 +5,7 @@ using WebClientTpos.Client; using WebClientTpos.Client.Localization; using Microsoft.Extensions.Localization; using System.Globalization; +using Microsoft.JSInterop; var builder = WebAssemblyHostBuilder.CreateDefault(args); @@ -27,22 +28,23 @@ builder.Services.AddSingleton(); -// Detect culture from BaseAddress (which is set by from Server) -var baseAddress = builder.HostEnvironment.BaseAddress; -var culture = new CultureInfo("en-US"); // Default +// Default culture is Vietnamese +var culture = new CultureInfo("vi-VN"); -if (baseAddress.Contains("/vi-VN/", StringComparison.OrdinalIgnoreCase)) +// Try reading saved culture preference from localStorage +try { - culture = new CultureInfo("vi-VN"); -} -else if (baseAddress.Contains("/vi/", StringComparison.OrdinalIgnoreCase)) -{ - culture = new CultureInfo("vi-VN"); + var jsRuntime = host.Services.GetRequiredService(); + var savedCulture = await jsRuntime.InvokeAsync("localStorage.getItem", "aPOS_culture"); + if (!string.IsNullOrEmpty(savedCulture)) + { + try { culture = new CultureInfo(savedCulture); } catch { } + } } +catch { /* JS not available yet during startup, use default */ } CultureInfo.DefaultThreadCurrentCulture = culture; CultureInfo.DefaultThreadCurrentUICulture = culture; diff --git a/apps/web-client-tpos-net/src/WebClientTpos.Client/wwwroot/css/app.css b/apps/web-client-tpos-net/src/WebClientTpos.Client/wwwroot/css/app.css index 7dfd3ae0..00325b52 100644 --- a/apps/web-client-tpos-net/src/WebClientTpos.Client/wwwroot/css/app.css +++ b/apps/web-client-tpos-net/src/WebClientTpos.Client/wwwroot/css/app.css @@ -340,21 +340,20 @@ a { .hero-mockup { width: 100%; - max-width: 900px; - height: 400px; + max-width: 1000px; margin: var(--space-16) auto 0; - background: var(--bg-surface); - border: 1px solid var(--border-default); + border: 1px solid var(--border-subtle); border-radius: var(--border-radius-2xl); - display: flex; - align-items: center; - justify-content: center; - color: var(--text-tertiary); - font-size: 0.875rem; overflow: hidden; box-shadow: 0 0 60px rgba(255, 92, 0, 0.08); } +.hero-mockup-img { + width: 100%; + height: auto; + display: block; +} + /* ═════════════════════════════════════════════════════════════════════════ 8. TRUST SECTION ═════════════════════════════════════════════════════════════════════════ */ @@ -531,25 +530,34 @@ a { } .tpos-industry-card { - background: var(--bg-surface); + background: var(--bg-page); border: 1px solid var(--border-subtle); border-radius: var(--border-radius-xl); - padding: var(--space-8); position: relative; overflow: hidden; transition: all 0.25s ease; + display: flex; + flex-direction: column; } -.tpos-industry-card::before { - content: ''; - position: absolute; - top: 0; - left: 0; - right: 0; - height: 3px; - background: var(--brand-gradient); - opacity: 0; - transition: opacity 0.25s ease; +.tpos-industry-img { + width: 100%; + height: 200px; + overflow: hidden; +} + +.tpos-industry-img img { + width: 100%; + height: 100%; + object-fit: cover; + display: block; +} + +.tpos-industry-content { + padding: var(--space-6); + display: flex; + flex-direction: column; + gap: var(--space-3); } .tpos-industry-card:hover { @@ -558,10 +566,6 @@ a { box-shadow: 0 8px 30px rgba(0, 0, 0, 0.3); } -.tpos-industry-card:hover::before { - opacity: 1; -} - .tpos-industry-title { font-size: 1.125rem; font-weight: 700; @@ -741,18 +745,23 @@ a { color: var(--text-secondary); } -.tpos-pricing-feature .check-icon { - font-size: 1rem; +.tpos-pricing-feature .lucide-check { + width: 16px; + height: 16px; flex-shrink: 0; - margin-top: 2px; + margin-top: 1px; + background-color: transparent !important; + background: none !important; } -.tpos-pricing-feature .check-icon.green { - color: var(--success); +.tpos-pricing-feature .lucide-check.green { + color: var(--success) !important; + stroke: var(--success); } -.tpos-pricing-feature .check-icon.accent { - color: var(--accent-primary); +.tpos-pricing-feature .lucide-check.accent { + color: var(--accent-primary) !important; + stroke: var(--accent-primary); } .tpos-pricing-divider { diff --git a/apps/web-client-tpos-net/src/WebClientTpos.Client/wwwroot/images/home/bar-ai.png b/apps/web-client-tpos-net/src/WebClientTpos.Client/wwwroot/images/home/bar-ai.png new file mode 100644 index 00000000..d72e535b Binary files /dev/null and b/apps/web-client-tpos-net/src/WebClientTpos.Client/wwwroot/images/home/bar-ai.png differ diff --git a/apps/web-client-tpos-net/src/WebClientTpos.Client/wwwroot/images/home/coffee-ai.png b/apps/web-client-tpos-net/src/WebClientTpos.Client/wwwroot/images/home/coffee-ai.png new file mode 100644 index 00000000..ce6f85e0 Binary files /dev/null and b/apps/web-client-tpos-net/src/WebClientTpos.Client/wwwroot/images/home/coffee-ai.png differ diff --git a/apps/web-client-tpos-net/src/WebClientTpos.Client/wwwroot/images/home/fnb-ai.png b/apps/web-client-tpos-net/src/WebClientTpos.Client/wwwroot/images/home/fnb-ai.png new file mode 100644 index 00000000..2cdd2a18 Binary files /dev/null and b/apps/web-client-tpos-net/src/WebClientTpos.Client/wwwroot/images/home/fnb-ai.png differ diff --git a/apps/web-client-tpos-net/src/WebClientTpos.Client/wwwroot/images/home/karaoke-ai.png b/apps/web-client-tpos-net/src/WebClientTpos.Client/wwwroot/images/home/karaoke-ai.png new file mode 100644 index 00000000..7f7722cc Binary files /dev/null and b/apps/web-client-tpos-net/src/WebClientTpos.Client/wwwroot/images/home/karaoke-ai.png differ diff --git a/apps/web-client-tpos-net/src/WebClientTpos.Client/wwwroot/images/home/pos-dashboard.png b/apps/web-client-tpos-net/src/WebClientTpos.Client/wwwroot/images/home/pos-dashboard.png new file mode 100644 index 00000000..ec8a7df8 Binary files /dev/null and b/apps/web-client-tpos-net/src/WebClientTpos.Client/wwwroot/images/home/pos-dashboard.png differ diff --git a/apps/web-client-tpos-net/src/WebClientTpos.Client/wwwroot/images/home/spa-ai.png b/apps/web-client-tpos-net/src/WebClientTpos.Client/wwwroot/images/home/spa-ai.png new file mode 100644 index 00000000..83ceff3a Binary files /dev/null and b/apps/web-client-tpos-net/src/WebClientTpos.Client/wwwroot/images/home/spa-ai.png differ diff --git a/pencil-design/src/pages/auth/forgot-password/desktop.pen b/pencil-design/src/pages/tPOS/auth/forgot-password/desktop.pen similarity index 100% rename from pencil-design/src/pages/auth/forgot-password/desktop.pen rename to pencil-design/src/pages/tPOS/auth/forgot-password/desktop.pen diff --git a/pencil-design/src/pages/auth/forgot-password/mobile.pen b/pencil-design/src/pages/tPOS/auth/forgot-password/mobile.pen similarity index 100% rename from pencil-design/src/pages/auth/forgot-password/mobile.pen rename to pencil-design/src/pages/tPOS/auth/forgot-password/mobile.pen diff --git a/pencil-design/src/pages/auth/forgot-password/tablet.pen b/pencil-design/src/pages/tPOS/auth/forgot-password/tablet.pen similarity index 100% rename from pencil-design/src/pages/auth/forgot-password/tablet.pen rename to pencil-design/src/pages/tPOS/auth/forgot-password/tablet.pen diff --git a/pencil-design/src/pages/auth/login/admin-desktop.pen b/pencil-design/src/pages/tPOS/auth/login/admin-desktop.pen similarity index 100% rename from pencil-design/src/pages/auth/login/admin-desktop.pen rename to pencil-design/src/pages/tPOS/auth/login/admin-desktop.pen diff --git a/pencil-design/src/pages/auth/login/admin-mobile.pen b/pencil-design/src/pages/tPOS/auth/login/admin-mobile.pen similarity index 100% rename from pencil-design/src/pages/auth/login/admin-mobile.pen rename to pencil-design/src/pages/tPOS/auth/login/admin-mobile.pen diff --git a/pencil-design/src/pages/auth/login/admin-tablet.pen b/pencil-design/src/pages/tPOS/auth/login/admin-tablet.pen similarity index 100% rename from pencil-design/src/pages/auth/login/admin-tablet.pen rename to pencil-design/src/pages/tPOS/auth/login/admin-tablet.pen diff --git a/pencil-design/src/pages/auth/login/branch-desktop.pen b/pencil-design/src/pages/tPOS/auth/login/branch-desktop.pen similarity index 100% rename from pencil-design/src/pages/auth/login/branch-desktop.pen rename to pencil-design/src/pages/tPOS/auth/login/branch-desktop.pen diff --git a/pencil-design/src/pages/auth/login/branch-mobile.pen b/pencil-design/src/pages/tPOS/auth/login/branch-mobile.pen similarity index 100% rename from pencil-design/src/pages/auth/login/branch-mobile.pen rename to pencil-design/src/pages/tPOS/auth/login/branch-mobile.pen diff --git a/pencil-design/src/pages/auth/login/branch-tablet.pen b/pencil-design/src/pages/tPOS/auth/login/branch-tablet.pen similarity index 100% rename from pencil-design/src/pages/auth/login/branch-tablet.pen rename to pencil-design/src/pages/tPOS/auth/login/branch-tablet.pen diff --git a/pencil-design/src/pages/auth/login/customer-desktop.pen b/pencil-design/src/pages/tPOS/auth/login/customer-desktop.pen similarity index 100% rename from pencil-design/src/pages/auth/login/customer-desktop.pen rename to pencil-design/src/pages/tPOS/auth/login/customer-desktop.pen diff --git a/pencil-design/src/pages/auth/login/customer-mobile.pen b/pencil-design/src/pages/tPOS/auth/login/customer-mobile.pen similarity index 100% rename from pencil-design/src/pages/auth/login/customer-mobile.pen rename to pencil-design/src/pages/tPOS/auth/login/customer-mobile.pen diff --git a/pencil-design/src/pages/auth/login/customer-tablet.pen b/pencil-design/src/pages/tPOS/auth/login/customer-tablet.pen similarity index 100% rename from pencil-design/src/pages/auth/login/customer-tablet.pen rename to pencil-design/src/pages/tPOS/auth/login/customer-tablet.pen diff --git a/pencil-design/src/pages/auth/login/staff-desktop.pen b/pencil-design/src/pages/tPOS/auth/login/staff-desktop.pen similarity index 100% rename from pencil-design/src/pages/auth/login/staff-desktop.pen rename to pencil-design/src/pages/tPOS/auth/login/staff-desktop.pen diff --git a/pencil-design/src/pages/auth/login/staff-mobile.pen b/pencil-design/src/pages/tPOS/auth/login/staff-mobile.pen similarity index 100% rename from pencil-design/src/pages/auth/login/staff-mobile.pen rename to pencil-design/src/pages/tPOS/auth/login/staff-mobile.pen diff --git a/pencil-design/src/pages/auth/login/staff-tablet.pen b/pencil-design/src/pages/tPOS/auth/login/staff-tablet.pen similarity index 100% rename from pencil-design/src/pages/auth/login/staff-tablet.pen rename to pencil-design/src/pages/tPOS/auth/login/staff-tablet.pen diff --git a/pencil-design/src/pages/auth/register/customer-desktop.pen b/pencil-design/src/pages/tPOS/auth/register/customer-desktop.pen similarity index 100% rename from pencil-design/src/pages/auth/register/customer-desktop.pen rename to pencil-design/src/pages/tPOS/auth/register/customer-desktop.pen diff --git a/pencil-design/src/pages/auth/register/customer-mobile.pen b/pencil-design/src/pages/tPOS/auth/register/customer-mobile.pen similarity index 100% rename from pencil-design/src/pages/auth/register/customer-mobile.pen rename to pencil-design/src/pages/tPOS/auth/register/customer-mobile.pen diff --git a/pencil-design/src/pages/auth/register/customer-tablet.pen b/pencil-design/src/pages/tPOS/auth/register/customer-tablet.pen similarity index 100% rename from pencil-design/src/pages/auth/register/customer-tablet.pen rename to pencil-design/src/pages/tPOS/auth/register/customer-tablet.pen diff --git a/pencil-design/src/pages/auth/workflow/email-sent.pen b/pencil-design/src/pages/tPOS/auth/workflow/email-sent.pen similarity index 100% rename from pencil-design/src/pages/auth/workflow/email-sent.pen rename to pencil-design/src/pages/tPOS/auth/workflow/email-sent.pen diff --git a/pencil-design/src/pages/auth/workflow/otp-verify.pen b/pencil-design/src/pages/tPOS/auth/workflow/otp-verify.pen similarity index 100% rename from pencil-design/src/pages/auth/workflow/otp-verify.pen rename to pencil-design/src/pages/tPOS/auth/workflow/otp-verify.pen diff --git a/pencil-design/src/pages/auth/workflow/password-reset.pen b/pencil-design/src/pages/tPOS/auth/workflow/password-reset.pen similarity index 100% rename from pencil-design/src/pages/auth/workflow/password-reset.pen rename to pencil-design/src/pages/tPOS/auth/workflow/password-reset.pen diff --git a/pencil-design/src/pages/auth/workflow/two-factor-auth.pen b/pencil-design/src/pages/tPOS/auth/workflow/two-factor-auth.pen similarity index 100% rename from pencil-design/src/pages/auth/workflow/two-factor-auth.pen rename to pencil-design/src/pages/tPOS/auth/workflow/two-factor-auth.pen