Files
pos-system/packages/blazor-ui/Components/Auth/AuthCard.razor
Ho Ngoc Hai af0461f233 fix(frontend): resolve 4 P2 architecture issues (Wave 3)
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>
2026-03-23 09:50:13 +07:00

158 lines
4.8 KiB
Plaintext

@*
EN: Reusable auth card wrapper — centered form container with icon, title, subtitle.
VI: Card xác thực tái sử dụng — container form giữa trang với icon, tiêu đề, mô tả.
*@
<div class="auth-card @(Compact ? "auth-card--compact" : "") @(Transparent ? "auth-card--transparent" : "") @CssClass">
@if (BackLink != null)
{
<a href="@BackLink" class="auth-back-link">
<i data-lucide="arrow-left"></i> @BackLinkText
</a>
}
@if (ShowHeader)
{
<div class="auth-header">
@if (Icon != null)
{
<div class="auth-icon @IconClass">
<i data-lucide="@Icon"></i>
</div>
}
@if (RoleBadge != null)
{
<span class="auth-role-badge @RoleBadgeClass">@RoleBadge</span>
}
@if (Title != null)
{
<h1 class="auth-heading">@Title</h1>
}
@if (Subtitle != null)
{
<p class="auth-subheading">@Subtitle</p>
}
@if (SecurityHint != null)
{
<div class="auth-security-hint @SecurityHintClass">
<i data-lucide="@SecurityHintIcon"></i>
@SecurityHint
</div>
}
</div>
}
@ChildContent
@if (FooterContent != null)
{
<div class="auth-footer">
@FooterContent
</div>
}
</div>
@code {
/// <summary>
/// EN: Card title text.
/// VI: Tiêu đề card.
/// </summary>
[Parameter] public string? Title { get; set; }
/// <summary>
/// EN: Subtitle/description text shown below the title.
/// VI: Mô tả hiển thị dưới tiêu đề.
/// </summary>
[Parameter] public string? Subtitle { get; set; }
/// <summary>
/// EN: Lucide icon name shown above the title.
/// VI: Tên icon Lucide hiển thị trên tiêu đề.
/// </summary>
[Parameter] public string? Icon { get; set; }
/// <summary>
/// EN: CSS class for the icon (e.g., auth-icon--blue, auth-icon--orange).
/// VI: CSS class cho icon (VD: auth-icon--blue, auth-icon--orange).
/// </summary>
[Parameter] public string IconClass { get; set; } = "auth-icon--blue";
/// <summary>
/// EN: Role badge text (e.g., "QUẢN TRỊ", "NHÂN VIÊN").
/// VI: Text badge vai trò (VD: "QUẢN TRỊ", "NHÂN VIÊN").
/// </summary>
[Parameter] public string? RoleBadge { get; set; }
/// <summary>
/// EN: CSS class for the role badge.
/// VI: CSS class cho role badge.
/// </summary>
[Parameter] public string RoleBadgeClass { get; set; } = "auth-role-badge--blue";
/// <summary>
/// EN: Security hint message (e.g., "Khu vực bảo mật cao").
/// VI: Thông báo bảo mật.
/// </summary>
[Parameter] public string? SecurityHint { get; set; }
/// <summary>
/// EN: Icon for the security hint.
/// VI: Icon cho thông báo bảo mật.
/// </summary>
[Parameter] public string SecurityHintIcon { get; set; } = "shield-alert";
/// <summary>
/// EN: CSS class for the security hint.
/// VI: CSS class cho thông báo bảo mật.
/// </summary>
[Parameter] public string SecurityHintClass { get; set; } = "auth-security-hint--warning";
/// <summary>
/// EN: Whether to show the header section.
/// VI: Có hiển thị phần header không.
/// </summary>
[Parameter] public bool ShowHeader { get; set; } = true;
/// <summary>
/// EN: Use compact gap (28px vs 32px).
/// VI: Dùng gap nhỏ hơn (28px thay vì 32px).
/// </summary>
[Parameter] public bool Compact { get; set; }
/// <summary>
/// EN: Transparent background (used inside split panels).
/// VI: Nền trong suốt (dùng trong split panel).
/// </summary>
[Parameter] public bool Transparent { get; set; }
/// <summary>
/// EN: Back link URL.
/// VI: URL link quay lại.
/// </summary>
[Parameter] public string? BackLink { get; set; }
/// <summary>
/// EN: Back link text.
/// VI: Text link quay lại.
/// </summary>
[Parameter] public string BackLinkText { get; set; } = "Quay lại đăng nhập";
/// <summary>
/// EN: Additional CSS classes.
/// VI: CSS class bổ sung.
/// </summary>
[Parameter] public string? CssClass { get; set; }
/// <summary>
/// EN: Main content slot.
/// VI: Slot nội dung chính.
/// </summary>
[Parameter] public RenderFragment? ChildContent { get; set; }
/// <summary>
/// EN: Footer content slot.
/// VI: Slot nội dung footer.
/// </summary>
[Parameter] public RenderFragment? FooterContent { get; set; }
}