FRONT-I-01: Extract Auth components to Razor Class Library packages/blazor-ui/
- Created GoodGo.BlazorUi RCL (net10.0, MudBlazor 8.15) at packages/blazor-ui/
- Moved AuthButton, AuthCard, AuthInput, OtpInput, BrandPanel, SocialLogin, LanguageSwitcher
- Referenced RCL from WebClientTpos.Client via ProjectReference
- Added GoodGo.BlazorUi.Components.Auth/Common namespaces to _Imports.razor
FRONT-I-02: Add ARIA/accessibility attributes (WCAG 2.1 AA)
- AuthButton: aria-label, aria-busy, aria-disabled, aria-hidden on decorative icons
- OtpInput: role=group, aria-label per digit, autocomplete=one-time-code
- PosLayout: aria-expanded + aria-controls on sidebar/order toggles, aria-label on all icon buttons
FRONT-I-03: Implement Style Dictionary design token pipeline
- Created packages/design-tokens/ with token JSON (color, spacing, typography, border)
- Style Dictionary config outputs: CSS custom properties → wwwroot/css/tokens.generated.css
- Second output: C# constants → packages/blazor-ui/DesignTokens/DesignTokens.g.cs
- Added tokens:build script to root package.json
- Added tokens.generated.css link to index.html (before app.css for cascade correctness)
FRONT-I-04: Replace eval() in OtpInput with safe JS interop
- Created wwwroot/js/otp-input.js with window.focusOtpInput(index) helper
- Replaced JS.InvokeVoidAsync("eval", ...) with JS.InvokeVoidAsync("focusOtpInput", index)
- Eliminates CSP-violating eval(), improves security and debuggability
Co-Authored-By: Paperclip <noreply@paperclip.ing>
67 lines
2.6 KiB
Plaintext
67 lines
2.6 KiB
Plaintext
@using System.Globalization
|
|
@inject NavigationManager Navigation
|
|
@inject IJSRuntime JS
|
|
|
|
<MudMenu Dense="true" AnchorOrigin="Origin.BottomRight" TransformOrigin="Origin.TopRight" LockScroll="true">
|
|
<ActivatorContent>
|
|
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1" Class="mr-2 cursor-pointer">
|
|
<MudText Typo="Typo.button" Style="font-family: var(--font-heading);">
|
|
@GetCurrentLabel()
|
|
</MudText>
|
|
<MudIcon Icon="@Icons.Material.Rounded.Language" Size="Size.Small" />
|
|
</MudStack>
|
|
</ActivatorContent>
|
|
<ChildContent>
|
|
<MudMenuItem OnClick="@(() => SwitchLanguage("vi-VN"))">
|
|
<MudStack Row="true" Spacing="2">
|
|
<MudText>🇻🇳</MudText>
|
|
<MudText>Tiếng Việt</MudText>
|
|
</MudStack>
|
|
</MudMenuItem>
|
|
<MudMenuItem OnClick="@(() => SwitchLanguage("en-US"))">
|
|
<MudStack Row="true" Spacing="2">
|
|
<MudText>🇺🇸</MudText>
|
|
<MudText>English</MudText>
|
|
</MudStack>
|
|
</MudMenuItem>
|
|
</ChildContent>
|
|
</MudMenu>
|
|
|
|
@code {
|
|
private string GetCurrentLabel()
|
|
{
|
|
var culture = CultureInfo.CurrentUICulture.Name;
|
|
return culture.StartsWith("vi", StringComparison.OrdinalIgnoreCase) ? "VI" : "EN";
|
|
}
|
|
|
|
private async Task SwitchLanguage(string targetCulture)
|
|
{
|
|
// 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);
|
|
|
|
// 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 = segments.Skip(1).ToArray();
|
|
}
|
|
|
|
var cleanPath = "/" + string.Join('/', segments);
|
|
if (cleanPath == "/") cleanPath = "";
|
|
|
|
// Navigate to root with forceLoad to reinitialize WASM
|
|
Navigation.NavigateTo($"{cleanPath}", forceLoad: true);
|
|
}
|
|
}
|