feat: Implement comprehensive authentication UI including login, registration, password reset, OTP verification, and supporting components.
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
@*
|
||||
EN: Auth CTA button with color variants (orange, blue, green, outline, ghost).
|
||||
VI: Nút CTA xác thực với các biến thể màu (cam, xanh dương, xanh lá, outline, ghost).
|
||||
*@
|
||||
|
||||
<button type="@ButtonType"
|
||||
class="auth-btn auth-btn--@Variant @CssClass"
|
||||
disabled="@Disabled"
|
||||
@onclick="OnClick">
|
||||
@if (Loading)
|
||||
{
|
||||
<span class="spinner-small"></span>
|
||||
}
|
||||
@if (IconName != null)
|
||||
{
|
||||
<i data-lucide="@IconName"></i>
|
||||
}
|
||||
@ChildContent
|
||||
</button>
|
||||
|
||||
@code {
|
||||
/// <summary>
|
||||
/// EN: Button color variant. Options: orange, blue, green, outline, ghost.
|
||||
/// VI: Biến thể màu. Tùy chọn: orange, blue, green, outline, ghost.
|
||||
/// </summary>
|
||||
[Parameter] public string Variant { get; set; } = "orange";
|
||||
|
||||
/// <summary>
|
||||
/// EN: HTML button type attribute.
|
||||
/// VI: Attribute type của button.
|
||||
/// </summary>
|
||||
[Parameter] public string ButtonType { get; set; } = "button";
|
||||
|
||||
/// <summary>
|
||||
/// EN: Whether the button is disabled.
|
||||
/// VI: Button có bị disable không.
|
||||
/// </summary>
|
||||
[Parameter] public bool Disabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// EN: Whether to show a loading spinner.
|
||||
/// VI: Có hiển thị spinner loading không.
|
||||
/// </summary>
|
||||
[Parameter] public bool Loading { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// EN: Optional Lucide icon name.
|
||||
/// VI: Tên icon Lucide tùy chọn.
|
||||
/// </summary>
|
||||
[Parameter] public string? IconName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// EN: Additional CSS classes.
|
||||
/// VI: CSS class bổ sung.
|
||||
/// </summary>
|
||||
[Parameter] public string? CssClass { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// EN: Click event callback.
|
||||
/// VI: Callback sự kiện click.
|
||||
/// </summary>
|
||||
[Parameter] public EventCallback<MouseEventArgs> OnClick { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// EN: Button content.
|
||||
/// VI: Nội dung button.
|
||||
/// </summary>
|
||||
[Parameter] public RenderFragment? ChildContent { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
@*
|
||||
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; }
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
@*
|
||||
EN: Dark-themed auth input with label, prefix icon, and action button (e.g., password toggle).
|
||||
VI: Input xác thực dark theme với label, icon prefix, và action button (VD: toggle mật khẩu).
|
||||
*@
|
||||
|
||||
<div class="auth-field">
|
||||
@if (Label != null || ForgotPasswordLink != null)
|
||||
{
|
||||
<div class="auth-label-row">
|
||||
@if (Label != null)
|
||||
{
|
||||
<label class="auth-label" for="@InputId">@Label</label>
|
||||
}
|
||||
@if (ForgotPasswordLink != null)
|
||||
{
|
||||
<a href="@ForgotPasswordLink" class="auth-label-link">@ForgotPasswordText</a>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<div class="auth-input-wrapper">
|
||||
@if (PrefixIcon != null)
|
||||
{
|
||||
<span class="auth-input-icon">
|
||||
<i data-lucide="@PrefixIcon"></i>
|
||||
</span>
|
||||
}
|
||||
<input id="@InputId"
|
||||
type="@_currentType"
|
||||
class="auth-input @(PrefixIcon == null ? "auth-input--no-icon" : "")"
|
||||
placeholder="@Placeholder"
|
||||
value="@Value"
|
||||
autocomplete="@AutoComplete"
|
||||
@oninput="OnInput"
|
||||
@onchange="OnChange" />
|
||||
@if (InputType == "password")
|
||||
{
|
||||
<button type="button" class="auth-input-action" @onclick="TogglePassword" title="Toggle password visibility">
|
||||
<i data-lucide="@(_showPassword ? "eye-off" : "eye")"></i>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private bool _showPassword = false;
|
||||
private string _currentType => InputType == "password" && _showPassword ? "text" : InputType;
|
||||
|
||||
[Parameter] public string? Label { get; set; }
|
||||
[Parameter] public string InputType { get; set; } = "text";
|
||||
[Parameter] public string? Placeholder { get; set; }
|
||||
[Parameter] public string? Value { get; set; }
|
||||
[Parameter] public string? PrefixIcon { get; set; }
|
||||
[Parameter] public string? InputId { get; set; }
|
||||
[Parameter] public string? AutoComplete { get; set; }
|
||||
[Parameter] public string? ForgotPasswordLink { get; set; }
|
||||
[Parameter] public string ForgotPasswordText { get; set; } = "Quên mật khẩu?";
|
||||
[Parameter] public EventCallback<ChangeEventArgs> ValueChanged { get; set; }
|
||||
|
||||
private async Task OnInput(ChangeEventArgs e)
|
||||
{
|
||||
Value = e.Value?.ToString();
|
||||
await ValueChanged.InvokeAsync(e);
|
||||
}
|
||||
|
||||
private async Task OnChange(ChangeEventArgs e)
|
||||
{
|
||||
Value = e.Value?.ToString();
|
||||
await ValueChanged.InvokeAsync(e);
|
||||
}
|
||||
|
||||
private void TogglePassword()
|
||||
{
|
||||
_showPassword = !_showPassword;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
@*
|
||||
EN: Brand panel for split auth layouts — shows logo, title, description, and trust stats.
|
||||
VI: Panel thương hiệu cho layout chia đôi — hiển thị logo, tiêu đề, mô tả, thống kê.
|
||||
*@
|
||||
|
||||
<div class="auth-brand-panel @PanelClass">
|
||||
<div class="auth-brand-logo">@LogoText</div>
|
||||
<h2 class="auth-brand-title">@Title</h2>
|
||||
<p class="auth-brand-desc">@Description</p>
|
||||
|
||||
@if (ShowStats)
|
||||
{
|
||||
<div class="auth-brand-stats">
|
||||
@foreach (var stat in Stats)
|
||||
{
|
||||
<div class="auth-brand-stat">
|
||||
<span class="auth-brand-stat-value">@stat.Value</span>
|
||||
<span class="auth-brand-stat-label">@stat.Label</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (Features != null && Features.Count > 0)
|
||||
{
|
||||
<div class="auth-brand-features">
|
||||
@foreach (var feature in Features)
|
||||
{
|
||||
<div class="auth-brand-feature">
|
||||
<i data-lucide="check"></i>
|
||||
<span>@feature</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@ChildContent
|
||||
</div>
|
||||
|
||||
@code {
|
||||
/// <summary>
|
||||
/// EN: Panel variant class (auth-brand-panel--orange or auth-brand-panel--dark).
|
||||
/// VI: Class biến thể panel (auth-brand-panel--orange hoặc auth-brand-panel--dark).
|
||||
/// </summary>
|
||||
[Parameter] public string PanelClass { get; set; } = "auth-brand-panel--orange";
|
||||
|
||||
/// <summary>
|
||||
/// EN: Logo character/text displayed in the brand panel.
|
||||
/// VI: Ký tự/text logo hiển thị trong brand panel.
|
||||
/// </summary>
|
||||
[Parameter] public string LogoText { get; set; } = "a";
|
||||
|
||||
/// <summary>
|
||||
/// EN: Brand panel title.
|
||||
/// VI: Tiêu đề brand panel.
|
||||
/// </summary>
|
||||
[Parameter] public string Title { get; set; } = "aPOS Branch";
|
||||
|
||||
/// <summary>
|
||||
/// EN: Description text.
|
||||
/// VI: Text mô tả.
|
||||
/// </summary>
|
||||
[Parameter] public string Description { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// EN: Whether to show trust stats.
|
||||
/// VI: Có hiển thị thống kê tin cậy không.
|
||||
/// </summary>
|
||||
[Parameter] public bool ShowStats { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Trust statistics data.
|
||||
/// VI: Dữ liệu thống kê tin cậy.
|
||||
/// </summary>
|
||||
[Parameter] public List<BrandStat> Stats { get; set; } = new()
|
||||
{
|
||||
new("1,200+", "Cửa hàng"),
|
||||
new("50K+", "Giao dịch/ngày"),
|
||||
new("99.9%", "Uptime")
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// EN: Feature list items.
|
||||
/// VI: Danh sách tính năng.
|
||||
/// </summary>
|
||||
[Parameter] public List<string>? Features { get; set; }
|
||||
|
||||
[Parameter] public RenderFragment? ChildContent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// EN: Brand stat record.
|
||||
/// VI: Record thống kê thương hiệu.
|
||||
/// </summary>
|
||||
public record BrandStat(string Value, string Label);
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
@*
|
||||
EN: 6-digit OTP input with auto-focus, auto-advance, and backspace support.
|
||||
VI: Input OTP 6 chữ số với auto-focus, tự chuyển ô, và hỗ trợ backspace.
|
||||
*@
|
||||
|
||||
@inject IJSRuntime JS
|
||||
|
||||
<div class="auth-otp-group">
|
||||
@for (int i = 0; i < DigitCount; i++)
|
||||
{
|
||||
var index = i;
|
||||
<input id="otp-@index"
|
||||
type="text"
|
||||
inputmode="numeric"
|
||||
maxlength="1"
|
||||
class="auth-otp-input @(UseBlueTheme ? "auth-otp-input--blue" : "") @(!string.IsNullOrEmpty(_digits[index]) ? "auth-otp-input--filled" : "")"
|
||||
value="@_digits[index]"
|
||||
@oninput="(e) => HandleInput(e, index)"
|
||||
@onkeydown="(e) => HandleKeyDown(e, index)" />
|
||||
}
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private string[] _digits = new string[6];
|
||||
|
||||
/// <summary>
|
||||
/// EN: Number of OTP digits.
|
||||
/// VI: Số chữ số OTP.
|
||||
/// </summary>
|
||||
[Parameter] public int DigitCount { get; set; } = 6;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Use blue theme (for 2FA authenticator).
|
||||
/// VI: Dùng theme xanh dương (cho 2FA authenticator).
|
||||
/// </summary>
|
||||
[Parameter] public bool UseBlueTheme { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// EN: Callback when all digits are entered.
|
||||
/// VI: Callback khi tất cả chữ số được nhập.
|
||||
/// </summary>
|
||||
[Parameter] public EventCallback<string> OnComplete { get; set; }
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
_digits = new string[DigitCount];
|
||||
}
|
||||
|
||||
private async Task HandleInput(ChangeEventArgs e, int index)
|
||||
{
|
||||
var value = e.Value?.ToString() ?? "";
|
||||
|
||||
// EN: Only allow numeric input
|
||||
// VI: Chỉ cho phép nhập số
|
||||
if (!string.IsNullOrEmpty(value) && !char.IsDigit(value[0]))
|
||||
{
|
||||
_digits[index] = "";
|
||||
return;
|
||||
}
|
||||
|
||||
_digits[index] = value;
|
||||
|
||||
if (!string.IsNullOrEmpty(value) && index < DigitCount - 1)
|
||||
{
|
||||
// EN: Auto-advance to next input
|
||||
// VI: Tự động chuyển sang ô tiếp theo
|
||||
await JS.InvokeVoidAsync("eval", $"document.getElementById('otp-{index + 1}')?.focus()");
|
||||
}
|
||||
|
||||
// EN: Check if all digits are filled
|
||||
// VI: Kiểm tra tất cả ô đã nhập xong chưa
|
||||
if (_digits.All(d => !string.IsNullOrEmpty(d)))
|
||||
{
|
||||
var code = string.Join("", _digits);
|
||||
await OnComplete.InvokeAsync(code);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleKeyDown(KeyboardEventArgs e, int index)
|
||||
{
|
||||
if (e.Key == "Backspace" && string.IsNullOrEmpty(_digits[index]) && index > 0)
|
||||
{
|
||||
// EN: Move to previous input on backspace
|
||||
// VI: Chuyển về ô trước khi nhấn backspace
|
||||
_digits[index - 1] = "";
|
||||
await JS.InvokeVoidAsync("eval", $"document.getElementById('otp-{index - 1}')?.focus()");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
@*
|
||||
EN: Social login buttons — Google, Apple, Zalo with divider.
|
||||
VI: Nút đăng nhập bên thứ ba — Google, Apple, Zalo với divider.
|
||||
*@
|
||||
|
||||
<div class="auth-divider">@DividerText</div>
|
||||
|
||||
<div class="auth-social-group">
|
||||
<button type="button" class="auth-social-btn auth-social-btn--google" title="Đăng nhập với Google" @onclick="OnGoogleClick">
|
||||
G
|
||||
</button>
|
||||
<button type="button" class="auth-social-btn auth-social-btn--apple" title="Đăng nhập với Apple" @onclick="OnAppleClick">
|
||||
|
||||
</button>
|
||||
@if (ShowZalo)
|
||||
{
|
||||
<button type="button" class="auth-social-btn auth-social-btn--zalo" title="Đăng nhập với Zalo" @onclick="OnZaloClick">
|
||||
Z
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
|
||||
@code {
|
||||
/// <summary>
|
||||
/// EN: Divider text between form and social buttons.
|
||||
/// VI: Text divider giữa form và social buttons.
|
||||
/// </summary>
|
||||
[Parameter] public string DividerText { get; set; } = "Hoặc đăng nhập bằng";
|
||||
|
||||
/// <summary>
|
||||
/// EN: Whether to show Zalo button (Vietnam market).
|
||||
/// VI: Có hiển thị nút Zalo không (thị trường VN).
|
||||
/// </summary>
|
||||
[Parameter] public bool ShowZalo { get; set; } = true;
|
||||
|
||||
[Parameter] public EventCallback OnGoogleClick { get; set; }
|
||||
[Parameter] public EventCallback OnAppleClick { get; set; }
|
||||
[Parameter] public EventCallback OnZaloClick { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
@page "/auth/email-sent"
|
||||
@using WebClientTpos.Client.Components.Auth
|
||||
@inject NavigationManager Navigation
|
||||
|
||||
@*
|
||||
EN: Email Sent Confirmation page — shows email delivery success with action buttons.
|
||||
VI: Trang xác nhận đã gửi email — hiển thị gửi email thành công với nút hành động.
|
||||
Design: pencil-design/src/pages/tPOS/auth/workflow/email-sent.pen
|
||||
*@
|
||||
|
||||
<PageTitle>Kiểm tra email của bạn</PageTitle>
|
||||
|
||||
<div class="auth-page">
|
||||
<AuthCard Title="Kiểm tra email của bạn"
|
||||
Subtitle="Chúng tôi đã gửi đường dẫn xác nhận đến email của bạn:"
|
||||
Icon="mail-check"
|
||||
IconClass="auth-icon--success"
|
||||
Compact="true">
|
||||
<ChildContent>
|
||||
<span class="auth-highlight">u****a@gmail.com</span>
|
||||
|
||||
<AuthButton Variant="orange" IconName="external-link">
|
||||
Mở ứng dụng Email
|
||||
</AuthButton>
|
||||
|
||||
<AuthButton Variant="outline" IconName="mail">
|
||||
Mở ứng dụng Email
|
||||
</AuthButton>
|
||||
</ChildContent>
|
||||
<FooterContent>
|
||||
<span class="auth-footer-text">
|
||||
Không nhận được email?
|
||||
</span>
|
||||
<span class="auth-footer-text">
|
||||
Kiểm tra spam hoặc <a href="#">gửi lại</a>
|
||||
</span>
|
||||
<a href="/auth/login/branch" class="auth-back-link" style="align-self: center;">
|
||||
<i data-lucide="arrow-left"></i> Quay lại đăng nhập
|
||||
</a>
|
||||
</FooterContent>
|
||||
</AuthCard>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
@page "/auth/forgot-password-new"
|
||||
@using WebClientTpos.Client.Components.Auth
|
||||
@inject NavigationManager Navigation
|
||||
|
||||
@*
|
||||
EN: Forgot password page — centered card, email/phone input → sends reset link.
|
||||
VI: Trang quên mật khẩu — card giữa, input email/SĐT → gửi link đặt lại.
|
||||
Design: pencil-design/src/pages/tPOS/auth/forgot-password/desktop.pen
|
||||
*@
|
||||
|
||||
<PageTitle>Quên mật khẩu</PageTitle>
|
||||
|
||||
<div class="auth-page">
|
||||
@if (!_resetSent)
|
||||
{
|
||||
<AuthCard Title="Quên mật khẩu?"
|
||||
Subtitle="Nhập email hoặc số điện thoại để nhận đường dẫn đặt lại mật khẩu."
|
||||
Icon="key-round"
|
||||
IconClass="auth-icon--orange"
|
||||
BackLink="/auth/login/branch"
|
||||
BackLinkText="Quay lại đăng nhập"
|
||||
Compact="true">
|
||||
<ChildContent>
|
||||
<div class="auth-form">
|
||||
<AuthInput Label="Email hoặc số điện thoại"
|
||||
InputType="text"
|
||||
Placeholder="Nhập email hoặc số điện thoại"
|
||||
PrefixIcon="mail"
|
||||
InputId="forgot-email" />
|
||||
</div>
|
||||
|
||||
<AuthButton Variant="orange" OnClick="HandleSubmit">
|
||||
Gửi yêu cầu
|
||||
</AuthButton>
|
||||
</ChildContent>
|
||||
</AuthCard>
|
||||
}
|
||||
else
|
||||
{
|
||||
<AuthCard Title="Kiểm tra hộp thư"
|
||||
Subtitle="@($"Chúng tôi đã gửi đường dẫn đặt lại mật khẩu đến:")"
|
||||
Icon="mail-check"
|
||||
IconClass="auth-icon--success">
|
||||
<ChildContent>
|
||||
<span class="auth-highlight">user@example.com</span>
|
||||
|
||||
<AuthButton Variant="orange" IconName="external-link">
|
||||
Mở ứng dụng email
|
||||
</AuthButton>
|
||||
|
||||
<AuthButton Variant="outline">
|
||||
Gửi lại email
|
||||
</AuthButton>
|
||||
</ChildContent>
|
||||
<FooterContent>
|
||||
<span class="auth-footer-text">
|
||||
Không nhận được email?
|
||||
</span>
|
||||
<span class="auth-footer-text">
|
||||
Kiểm tra spam hoặc <a href="/auth/forgot-password-new">thử lại với số khác</a>
|
||||
</span>
|
||||
</FooterContent>
|
||||
</AuthCard>
|
||||
}
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private bool _resetSent = false;
|
||||
|
||||
private void HandleSubmit()
|
||||
{
|
||||
_resetSent = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
@page "/auth/login/admin"
|
||||
@using WebClientTpos.Client.Components.Auth
|
||||
@inject NavigationManager Navigation
|
||||
|
||||
@*
|
||||
EN: Admin login page — centered card layout, blue CTA, security badge.
|
||||
VI: Trang đăng nhập Admin — layout card giữa, CTA xanh dương, badge bảo mật.
|
||||
Design: pencil-design/src/pages/tPOS/auth/login/admin-desktop.pen
|
||||
*@
|
||||
|
||||
<PageTitle>Đăng nhập Admin</PageTitle>
|
||||
|
||||
<div class="auth-page">
|
||||
<AuthCard Title="Đăng nhập Admin"
|
||||
Subtitle="Truy cập hệ thống quản trị aPOS"
|
||||
Icon="shield"
|
||||
IconClass="auth-icon--blue"
|
||||
RoleBadge="QUẢN TRỊ"
|
||||
RoleBadgeClass="auth-role-badge--blue"
|
||||
SecurityHint="Khu vực bảo mật cao"
|
||||
SecurityHintIcon="shield-alert"
|
||||
SecurityHintClass="auth-security-hint--warning">
|
||||
<ChildContent>
|
||||
<div class="auth-form">
|
||||
<AuthInput Label="Email quản trị viên"
|
||||
InputType="email"
|
||||
Placeholder="admin@company.com"
|
||||
PrefixIcon="mail"
|
||||
InputId="admin-email"
|
||||
AutoComplete="email" />
|
||||
|
||||
<AuthInput Label="Mật khẩu"
|
||||
InputType="password"
|
||||
Placeholder="••••••••"
|
||||
PrefixIcon="lock"
|
||||
InputId="admin-password"
|
||||
AutoComplete="current-password" />
|
||||
</div>
|
||||
|
||||
<AuthButton Variant="blue" IconName="shield">
|
||||
Đăng nhập bảo mật
|
||||
</AuthButton>
|
||||
</ChildContent>
|
||||
<FooterContent>
|
||||
<div class="auth-role-hints">
|
||||
<span class="auth-role-hints-title">Đăng nhập với vai trò khác</span>
|
||||
<div class="auth-role-list">
|
||||
<a href="/auth/login/branch" class="auth-role-item">
|
||||
<i data-lucide="store"></i> Chi nhánh
|
||||
</a>
|
||||
<a href="/auth/login/staff" class="auth-role-item">
|
||||
<i data-lucide="user"></i> Nhân viên
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</FooterContent>
|
||||
</AuthCard>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
@page "/auth/login/branch"
|
||||
@using WebClientTpos.Client.Components.Auth
|
||||
@inject NavigationManager Navigation
|
||||
|
||||
@*
|
||||
EN: Branch login page — split panel layout, orange CTA, trust stats.
|
||||
VI: Trang đăng nhập Chi nhánh — layout chia đôi, CTA cam, thống kê tin cậy.
|
||||
Design: pencil-design/src/pages/tPOS/auth/login/branch-desktop.pen
|
||||
*@
|
||||
|
||||
<PageTitle>Đăng nhập Chi nhánh</PageTitle>
|
||||
|
||||
<div class="auth-page--split">
|
||||
<BrandPanel PanelClass="auth-brand-panel--orange"
|
||||
LogoText="a"
|
||||
Title="aPOS Branch"
|
||||
Description="Quản lý và theo dõi chi nhánh của bạn. Truy cập báo cáo doanh thu, quản lý nhân viên và tồn kho hiệu quả." />
|
||||
|
||||
<div class="auth-form-panel">
|
||||
<AuthCard Title="Đăng nhập Chi nhánh"
|
||||
Subtitle="Quản lý và theo dõi chi nhánh của bạn"
|
||||
Transparent="true">
|
||||
<ChildContent>
|
||||
<div class="auth-form">
|
||||
<AuthInput Label="Email hoặc số điện thoại"
|
||||
InputType="text"
|
||||
Placeholder="branch@company.com"
|
||||
PrefixIcon="mail"
|
||||
InputId="branch-email"
|
||||
AutoComplete="email" />
|
||||
|
||||
<AuthInput Label="Mật khẩu"
|
||||
InputType="password"
|
||||
Placeholder="••••••••"
|
||||
PrefixIcon="lock"
|
||||
InputId="branch-password"
|
||||
AutoComplete="current-password"
|
||||
ForgotPasswordLink="/auth/forgot-password"
|
||||
ForgotPasswordText="Quên mật khẩu?" />
|
||||
</div>
|
||||
|
||||
<AuthButton Variant="orange">
|
||||
Đăng nhập
|
||||
</AuthButton>
|
||||
</ChildContent>
|
||||
<FooterContent>
|
||||
<span class="auth-footer-text">
|
||||
Chưa có tài khoản? <a href="/auth/register">Liên hệ</a>
|
||||
</span>
|
||||
</FooterContent>
|
||||
</AuthCard>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
@page "/auth/login/customer"
|
||||
@using WebClientTpos.Client.Components.Auth
|
||||
@inject NavigationManager Navigation
|
||||
|
||||
@*
|
||||
EN: Customer login page — split panel layout, phone OTP + social login.
|
||||
VI: Trang đăng nhập Khách hàng — layout chia đôi, OTP điện thoại + social login.
|
||||
Design: pencil-design/src/pages/tPOS/auth/login/customer-desktop.pen
|
||||
*@
|
||||
|
||||
<PageTitle>Đăng nhập Khách hàng</PageTitle>
|
||||
|
||||
<div class="auth-page--split">
|
||||
<BrandPanel PanelClass="auth-brand-panel--dark"
|
||||
LogoText="G"
|
||||
Title="GoodGo"
|
||||
Description="Mua sắm thông minh, tích điểm đổi quà. Trải nghiệm thanh toán nhanh chóng và tiện lợi."
|
||||
ShowStats="false"
|
||||
Features="@_features" />
|
||||
|
||||
<div class="auth-form-panel">
|
||||
<AuthCard Title="Chào mừng bạn!"
|
||||
Subtitle="Đăng nhập để nhận ưu đãi và tích điểm thưởng"
|
||||
Transparent="true">
|
||||
<ChildContent>
|
||||
<div class="auth-form">
|
||||
<div class="auth-field">
|
||||
<label class="auth-label">Số điện thoại</label>
|
||||
<div class="auth-phone-group">
|
||||
<span class="auth-phone-prefix">🇻🇳 +84</span>
|
||||
<input type="tel"
|
||||
class="auth-phone-input"
|
||||
placeholder="912 345 678"
|
||||
id="customer-phone"
|
||||
autocomplete="tel" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<AuthButton Variant="orange" IconName="arrow-right">
|
||||
Nhận mã OTP
|
||||
</AuthButton>
|
||||
|
||||
<SocialLogin DividerText="Hoặc đăng nhập bằng" ShowZalo="true" />
|
||||
</ChildContent>
|
||||
<FooterContent>
|
||||
<span class="auth-footer-text">
|
||||
Chưa có tài khoản? <a href="/auth/register/customer">Đăng ký ngay</a>
|
||||
</span>
|
||||
<p class="auth-disclaimer">
|
||||
Bằng việc tiếp tục, bạn đồng ý với
|
||||
<a href="/terms">Điều khoản sử dụng</a> và
|
||||
<a href="/privacy">Chính sách bảo mật</a>
|
||||
</p>
|
||||
</FooterContent>
|
||||
</AuthCard>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private List<string> _features = new()
|
||||
{
|
||||
"Tích điểm tự động mỗi lần mua hàng",
|
||||
"Đổi điểm lấy quà tặng hấp dẫn",
|
||||
"Nhận thông báo ưu đãi độc quyền",
|
||||
"Quản lý đơn hàng và lịch sử giao dịch"
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
@page "/auth/login/staff"
|
||||
@using WebClientTpos.Client.Components.Auth
|
||||
@inject NavigationManager Navigation
|
||||
|
||||
@*
|
||||
EN: Staff login page — centered card layout, green CTA, role hints.
|
||||
VI: Trang đăng nhập Nhân viên — layout card giữa, CTA xanh lá, gợi ý vai trò.
|
||||
Design: pencil-design/src/pages/tPOS/auth/login/staff-desktop.pen
|
||||
*@
|
||||
|
||||
<PageTitle>Đăng nhập Nhân viên</PageTitle>
|
||||
|
||||
<div class="auth-page">
|
||||
<AuthCard Title="Đăng nhập Nhân viên"
|
||||
Subtitle="Nhập mã nhân viên hoặc email để bắt đầu ca làm việc"
|
||||
Icon="user-check"
|
||||
IconClass="auth-icon--green"
|
||||
RoleBadge="NHÂN VIÊN"
|
||||
RoleBadgeClass="auth-role-badge--green"
|
||||
Compact="true">
|
||||
<ChildContent>
|
||||
<div class="auth-form">
|
||||
<AuthInput Label="Mã nhân viên hoặc email"
|
||||
InputType="text"
|
||||
Placeholder="NV001 hoặc staff@company.com"
|
||||
PrefixIcon="user"
|
||||
InputId="staff-id"
|
||||
AutoComplete="username" />
|
||||
|
||||
<AuthInput Label="Mật khẩu"
|
||||
InputType="password"
|
||||
Placeholder="••••••••"
|
||||
PrefixIcon="lock"
|
||||
InputId="staff-password"
|
||||
AutoComplete="current-password" />
|
||||
</div>
|
||||
|
||||
<AuthButton Variant="green" IconName="log-in">
|
||||
Đăng nhập ca làm việc
|
||||
</AuthButton>
|
||||
</ChildContent>
|
||||
<FooterContent>
|
||||
<div class="auth-role-hints">
|
||||
<span class="auth-role-hints-title">Vai trò hỗ trợ</span>
|
||||
<div class="auth-role-list">
|
||||
<span class="auth-role-item">
|
||||
<i data-lucide="monitor"></i> Thu ngân
|
||||
</span>
|
||||
<span class="auth-role-item">
|
||||
<i data-lucide="coffee"></i> Barista
|
||||
</span>
|
||||
<span class="auth-role-item">
|
||||
<i data-lucide="clipboard-list"></i> Phục vụ
|
||||
</span>
|
||||
<span class="auth-role-item">
|
||||
<i data-lucide="settings"></i> Quản lý
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<span class="auth-footer-text">
|
||||
Quên mật khẩu? <a href="/auth/forgot-password">Liên hệ quản lý</a>
|
||||
</span>
|
||||
</FooterContent>
|
||||
</AuthCard>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
@page "/auth/otp-verify"
|
||||
@using WebClientTpos.Client.Components.Auth
|
||||
@inject NavigationManager Navigation
|
||||
|
||||
@*
|
||||
EN: OTP Verification page — 6-digit code input with countdown timer.
|
||||
VI: Trang xác thực OTP — input 6 chữ số với đếm ngược.
|
||||
Design: pencil-design/src/pages/tPOS/auth/workflow/otp-verify.pen
|
||||
*@
|
||||
|
||||
<PageTitle>Xác thực OTP</PageTitle>
|
||||
|
||||
<div class="auth-page">
|
||||
<AuthCard Title="Xác thực OTP"
|
||||
Subtitle="Nhập mã 6 số đã gửi đến điện thoại của bạn *** 679"
|
||||
Icon="smartphone"
|
||||
IconClass="auth-icon--green"
|
||||
Compact="true">
|
||||
<ChildContent>
|
||||
<OtpInput OnComplete="HandleOtpComplete" />
|
||||
|
||||
<AuthButton Variant="orange">
|
||||
Xác nhận
|
||||
</AuthButton>
|
||||
</ChildContent>
|
||||
<FooterContent>
|
||||
<div class="auth-timer">
|
||||
<i data-lucide="clock"></i>
|
||||
Mã hết hạn sau: <span class="auth-timer-value">04:30</span>
|
||||
</div>
|
||||
<span class="auth-footer-text">
|
||||
Không nhận được mã? <a href="#">Gửi lại</a>
|
||||
</span>
|
||||
</FooterContent>
|
||||
</AuthCard>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private async Task HandleOtpComplete(string code)
|
||||
{
|
||||
Console.WriteLine($"OTP entered: {code}");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
@page "/auth/password-reset"
|
||||
@using WebClientTpos.Client.Components.Auth
|
||||
@inject NavigationManager Navigation
|
||||
|
||||
@*
|
||||
EN: Password Reset page — new password form with strength indicator.
|
||||
VI: Trang đặt lại mật khẩu — form mật khẩu mới với chỉ báo độ mạnh.
|
||||
Design: pencil-design/src/pages/tPOS/auth/workflow/password-reset.pen
|
||||
*@
|
||||
|
||||
<PageTitle>Đặt lại mật khẩu</PageTitle>
|
||||
|
||||
<div class="auth-page">
|
||||
@if (!_resetSuccess)
|
||||
{
|
||||
<AuthCard Title="Đặt lại mật khẩu"
|
||||
Subtitle="Tạo mật khẩu mới cho tài khoản của bạn"
|
||||
Icon="lock"
|
||||
IconClass="auth-icon--blue"
|
||||
BackLink="/auth/forgot-password-new"
|
||||
BackLinkText="Quay lại"
|
||||
Compact="true">
|
||||
<ChildContent>
|
||||
<div class="auth-form">
|
||||
<AuthInput Label="Mật khẩu mới"
|
||||
InputType="password"
|
||||
Placeholder="Tối thiểu 8 ký tự"
|
||||
PrefixIcon="lock"
|
||||
InputId="new-password" />
|
||||
|
||||
<AuthInput Label="Xác nhận mật khẩu"
|
||||
InputType="password"
|
||||
Placeholder="Nhập lại mật khẩu"
|
||||
PrefixIcon="lock"
|
||||
InputId="confirm-password" />
|
||||
|
||||
<div class="auth-strength">
|
||||
<span class="auth-strength-title">Yêu cầu mật khẩu</span>
|
||||
<div class="auth-strength-item auth-strength-item--pass">
|
||||
<i data-lucide="check-circle"></i> Ít nhất 8 ký tự
|
||||
</div>
|
||||
<div class="auth-strength-item auth-strength-item--pass">
|
||||
<i data-lucide="check-circle"></i> Chứa chữ hoa và chữ thường
|
||||
</div>
|
||||
<div class="auth-strength-item">
|
||||
<i data-lucide="circle"></i> Chứa ít nhất 1 số hoặc 1 ký tự đặc biệt
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<AuthButton Variant="orange" OnClick="HandleReset">
|
||||
Đặt lại mật khẩu
|
||||
</AuthButton>
|
||||
</ChildContent>
|
||||
</AuthCard>
|
||||
}
|
||||
else
|
||||
{
|
||||
<AuthCard Title="Đặt lại thành công!"
|
||||
Subtitle="Mật khẩu của bạn đã được cập nhật. Bạn có thể đăng nhập với mật khẩu mới."
|
||||
Icon="check-circle"
|
||||
IconClass="auth-icon--success">
|
||||
<ChildContent>
|
||||
<AuthButton Variant="orange" OnClick="GoToLogin">
|
||||
Đăng nhập ngay
|
||||
</AuthButton>
|
||||
</ChildContent>
|
||||
</AuthCard>
|
||||
}
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private bool _resetSuccess = false;
|
||||
|
||||
private void HandleReset()
|
||||
{
|
||||
_resetSuccess = true;
|
||||
}
|
||||
|
||||
private void GoToLogin()
|
||||
{
|
||||
Navigation.NavigateTo("/auth/login/branch");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
@page "/auth/register/customer"
|
||||
@using WebClientTpos.Client.Components.Auth
|
||||
@inject NavigationManager Navigation
|
||||
|
||||
@*
|
||||
EN: Customer registration page — split panel layout, phone + social registration.
|
||||
VI: Trang đăng ký Khách hàng — layout chia đôi, đăng ký bằng SĐT + mạng xã hội.
|
||||
Design: pencil-design/src/pages/tPOS/auth/register/customer-desktop.pen
|
||||
*@
|
||||
|
||||
<PageTitle>Đăng ký tài khoản</PageTitle>
|
||||
|
||||
<div class="auth-page--split">
|
||||
<BrandPanel PanelClass="auth-brand-panel--dark"
|
||||
LogoText="G"
|
||||
Title="Tham gia GoodGo"
|
||||
Description="Tạo tài khoản để tích điểm, nhận ưu đãi độc quyền và theo dõi đơn hàng dễ dàng."
|
||||
ShowStats="false"
|
||||
Features="@_features" />
|
||||
|
||||
<div class="auth-form-panel">
|
||||
<AuthCard Title="Tạo tài khoản"
|
||||
Subtitle="Điền thông tin để bắt đầu tích điểm và nhận ưu đãi"
|
||||
Transparent="true">
|
||||
<ChildContent>
|
||||
<div class="auth-form">
|
||||
<AuthInput Label="Họ và tên"
|
||||
InputType="text"
|
||||
Placeholder="Nguyễn Văn A"
|
||||
PrefixIcon="user"
|
||||
InputId="reg-name"
|
||||
AutoComplete="name" />
|
||||
|
||||
<div class="auth-field">
|
||||
<label class="auth-label">Số điện thoại</label>
|
||||
<div class="auth-phone-group">
|
||||
<span class="auth-phone-prefix">🇻🇳 +84</span>
|
||||
<input type="tel"
|
||||
class="auth-phone-input"
|
||||
placeholder="912 345 678"
|
||||
id="reg-phone"
|
||||
autocomplete="tel" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<AuthInput Label="Email (tuỳ chọn)"
|
||||
InputType="email"
|
||||
Placeholder="email@example.com"
|
||||
PrefixIcon="mail"
|
||||
InputId="reg-email"
|
||||
AutoComplete="email" />
|
||||
|
||||
<div class="auth-checkbox-row">
|
||||
<input type="checkbox" id="reg-terms" />
|
||||
<label for="reg-terms">
|
||||
Tôi đồng ý với <a href="/terms">Điều khoản sử dụng</a>
|
||||
và <a href="/privacy">Chính sách bảo mật</a>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<AuthButton Variant="orange" IconName="user-plus">
|
||||
Đăng ký
|
||||
</AuthButton>
|
||||
|
||||
<SocialLogin DividerText="Hoặc đăng ký bằng" ShowZalo="true" />
|
||||
</ChildContent>
|
||||
<FooterContent>
|
||||
<span class="auth-footer-text">
|
||||
Đã có tài khoản? <a href="/auth/login/customer">Đăng nhập</a>
|
||||
</span>
|
||||
</FooterContent>
|
||||
</AuthCard>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private List<string> _features = new()
|
||||
{
|
||||
"Đăng ký nhanh trong 30 giây",
|
||||
"Nhận ngay 100 điểm thưởng",
|
||||
"Ưu đãi sinh nhật đặc biệt",
|
||||
"Lịch sử mua hàng & đơn hàng"
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
@page "/auth/two-factor"
|
||||
@using WebClientTpos.Client.Components.Auth
|
||||
@inject NavigationManager Navigation
|
||||
|
||||
@*
|
||||
EN: Two-Factor Authentication page — 6-digit authenticator code input, blue theme.
|
||||
VI: Trang xác thực 2 lớp — input 6 chữ số từ authenticator app, theme xanh dương.
|
||||
Design: pencil-design/src/pages/tPOS/auth/workflow/two-factor-auth.pen
|
||||
*@
|
||||
|
||||
<PageTitle>Xác thực 2 lớp</PageTitle>
|
||||
|
||||
<div class="auth-page">
|
||||
<AuthCard Title="Xác thực 2 lớp"
|
||||
Subtitle="Nhập mã 6 số từ ứng dụng Google Authenticator hoặc Authy"
|
||||
Icon="shield-check"
|
||||
IconClass="auth-icon--blue"
|
||||
Compact="true">
|
||||
<ChildContent>
|
||||
<OtpInput UseBlueTheme="true" OnComplete="Handle2FAComplete" />
|
||||
|
||||
<AuthButton Variant="blue">
|
||||
Xác nhận
|
||||
</AuthButton>
|
||||
</ChildContent>
|
||||
<FooterContent>
|
||||
<span class="auth-footer-text">
|
||||
Không thể sử dụng Authenticator? <a href="#" class="auth-footer-link--blue">Dùng backup code</a>
|
||||
</span>
|
||||
</FooterContent>
|
||||
</AuthCard>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private async Task Handle2FAComplete(string code)
|
||||
{
|
||||
Console.WriteLine($"2FA code entered: {code}");
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -30,6 +30,7 @@
|
||||
<!-- EN: Custom CSS -->
|
||||
<!-- VI: CSS tùy chỉnh -->
|
||||
<link rel="stylesheet" href="/css/app.css" />
|
||||
<link rel="stylesheet" href="/css/auth.css" />
|
||||
<link rel="icon" type="image/png" href="/favicon.png" />
|
||||
<link href="/WebClientTpos.Client.styles.css" rel="stylesheet" />
|
||||
</head>
|
||||
|
||||
Reference in New Issue
Block a user