feat: Set up initial WebClientTpos .NET project structure, including client, server, assets, and documentation.

This commit is contained in:
Ho Ngoc Hai
2026-02-12 00:41:43 +07:00
parent b661bb7d8b
commit 689f4fa96f
51 changed files with 4860 additions and 0 deletions

View File

@@ -0,0 +1,58 @@
namespace WebClientTpos.Shared;
/// <summary>
/// EN: Standard API response wrapper for consistent response format.
/// VI: Wrapper response API chuẩn cho định dạng response nhất quán.
/// </summary>
/// <typeparam name="T">The type of data in the response.</typeparam>
public class ApiResponse<T>
{
/// <summary>
/// EN: Indicates if the request was successful.
/// VI: Cho biết request có thành công không.
/// </summary>
public bool Success { get; set; }
/// <summary>
/// EN: The response data.
/// VI: Dữ liệu response.
/// </summary>
public T? Data { get; set; }
/// <summary>
/// EN: Error message if request failed.
/// VI: Thông báo lỗi nếu request thất bại.
/// </summary>
public string? Error { get; set; }
/// <summary>
/// EN: Creates a successful response with data.
/// VI: Tạo response thành công với dữ liệu.
/// </summary>
public static ApiResponse<T> Ok(T data) => new() { Success = true, Data = data };
/// <summary>
/// EN: Creates a failed response with error message.
/// VI: Tạo response thất bại với thông báo lỗi.
/// </summary>
public static ApiResponse<T> Fail(string error) => new() { Success = false, Error = error };
}
/// <summary>
/// EN: Non-generic API response for operations without return data.
/// VI: API response không generic cho các operation không có dữ liệu trả về.
/// </summary>
public class ApiResponse : ApiResponse<object>
{
/// <summary>
/// EN: Creates a successful response.
/// VI: Tạo response thành công.
/// </summary>
public static new ApiResponse Ok() => new() { Success = true };
/// <summary>
/// EN: Creates a failed response with error message.
/// VI: Tạo response thất bại với thông báo lỗi.
/// </summary>
public static new ApiResponse Fail(string error) => new() { Success = false, Error = error };
}

View File

@@ -0,0 +1,23 @@
using System.ComponentModel.DataAnnotations;
namespace WebClientTpos.Shared.DTOs;
/// <summary>
/// EN: Change password DTO for authenticated users.
/// VI: DTO cho đổi mật khẩu của người dùng đã xác thực.
/// </summary>
public class ChangePasswordDto
{
[Required(ErrorMessage = "Current password is required")]
public string CurrentPassword { get; set; } = string.Empty;
[Required(ErrorMessage = "New password is required")]
[MinLength(8, ErrorMessage = "Password must be at least 8 characters")]
[RegularExpression(@"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^\da-zA-Z]).+$",
ErrorMessage = "Password must contain uppercase, lowercase, digit, and special character")]
public string NewPassword { get; set; } = string.Empty;
[Required(ErrorMessage = "Confirm password is required")]
[Compare(nameof(NewPassword), ErrorMessage = "Passwords do not match")]
public string ConfirmPassword { get; set; } = string.Empty;
}

View File

@@ -0,0 +1,14 @@
using System.ComponentModel.DataAnnotations;
namespace WebClientTpos.Shared.DTOs;
/// <summary>
/// EN: Forgot password request DTO.
/// VI: DTO cho yêu cầu quên mật khẩu.
/// </summary>
public class ForgotPasswordDto
{
[Required(ErrorMessage = "Email is required")]
[EmailAddress(ErrorMessage = "Invalid email address")]
public string Email { get; set; } = string.Empty;
}

View File

@@ -0,0 +1,72 @@
using System.ComponentModel.DataAnnotations;
namespace WebClientTpos.Shared.DTOs;
/// <summary>
/// EN: Product data transfer object with validation.
/// VI: DTO sản phẩm với validation.
/// </summary>
/// <remarks>
/// EN: This DTO is shared between Client and Server for consistent validation.
/// VI: DTO này được chia sẻ giữa Client và Server để validation nhất quán.
/// </remarks>
public class ProductDto
{
/// <summary>
/// EN: Product name, required with length constraints.
/// VI: Tên sản phẩm, bắt buộc với ràng buộc độ dài.
/// </summary>
[Required(ErrorMessage = "Tên sản phẩm là bắt buộc / Name is required")]
[StringLength(100, MinimumLength = 3, ErrorMessage = "Tên phải từ 3-100 ký tự / Name must be 3-100 chars")]
public string Name { get; set; } = string.Empty;
/// <summary>
/// EN: Product description.
/// VI: Mô tả sản phẩm.
/// </summary>
[StringLength(500, ErrorMessage = "Mô tả tối đa 500 ký tự / Max 500 characters")]
public string? Description { get; set; }
/// <summary>
/// EN: Product price, must be positive.
/// VI: Giá sản phẩm, phải dương.
/// </summary>
[Required(ErrorMessage = "Giá là bắt buộc / Price is required")]
[Range(0.01, 1_000_000_000, ErrorMessage = "Giá phải từ 0.01 đến 1 tỷ / Price must be 0.01 to 1B")]
public decimal Price { get; set; }
/// <summary>
/// EN: Stock quantity.
/// VI: Số lượng tồn kho.
/// </summary>
[Range(0, int.MaxValue, ErrorMessage = "Số lượng không âm / Quantity must be non-negative")]
public int Quantity { get; set; }
}
/// <summary>
/// EN: Create product request DTO.
/// VI: DTO request tạo sản phẩm.
/// </summary>
public class CreateProductDto : ProductDto
{
/// <summary>
/// EN: Product category ID.
/// VI: ID danh mục sản phẩm.
/// </summary>
[Required(ErrorMessage = "Danh mục là bắt buộc / Category is required")]
public Guid CategoryId { get; set; }
}
/// <summary>
/// EN: Update product request DTO.
/// VI: DTO request cập nhật sản phẩm.
/// </summary>
public class UpdateProductDto : ProductDto
{
/// <summary>
/// EN: Product ID.
/// VI: ID sản phẩm.
/// </summary>
[Required]
public Guid Id { get; set; }
}

View File

@@ -0,0 +1,27 @@
using System.ComponentModel.DataAnnotations;
namespace WebClientTpos.Shared.DTOs;
/// <summary>
/// EN: Reset password DTO.
/// VI: DTO cho đặt lại mật khẩu.
/// </summary>
public class ResetPasswordDto
{
[Required(ErrorMessage = "Token is required")]
public string Token { get; set; } = string.Empty;
[Required(ErrorMessage = "Email is required")]
[EmailAddress(ErrorMessage = "Invalid email address")]
public string Email { get; set; } = string.Empty;
[Required(ErrorMessage = "Password is required")]
[MinLength(8, ErrorMessage = "Password must be at least 8 characters")]
[RegularExpression(@"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^\da-zA-Z]).+$",
ErrorMessage = "Password must contain uppercase, lowercase, digit, and special character")]
public string NewPassword { get; set; } = string.Empty;
[Required(ErrorMessage = "Confirm password is required")]
[Compare(nameof(NewPassword), ErrorMessage = "Passwords do not match")]
public string ConfirmPassword { get; set; } = string.Empty;
}

View File

@@ -0,0 +1,93 @@
using System.ComponentModel.DataAnnotations;
namespace WebClientTpos.Shared.DTOs;
/// <summary>
/// EN: User registration DTO with validation.
/// VI: DTO đăng ký user với validation.
/// </summary>
public class RegisterDto
{
/// <summary>
/// EN: User email address.
/// VI: Địa chỉ email user.
/// </summary>
[Required(ErrorMessage = "Email là bắt buộc / Email is required")]
[EmailAddress(ErrorMessage = "Email không hợp lệ / Invalid email format")]
public string Email { get; set; } = string.Empty;
/// <summary>
/// EN: User password with strength requirements.
/// VI: Mật khẩu user với yêu cầu độ mạnh.
/// </summary>
[Required(ErrorMessage = "Mật khẩu là bắt buộc / Password is required")]
[StringLength(100, MinimumLength = 8, ErrorMessage = "Mật khẩu phải từ 8-100 ký tự / Password must be 8-100 chars")]
[RegularExpression(@"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).+$",
ErrorMessage = "Mật khẩu phải chữ hoa, chữ thường số / Password must have upper, lower and digit")]
public string Password { get; set; } = string.Empty;
/// <summary>
/// EN: Password confirmation must match.
/// VI: Xác nhận mật khẩu phải khớp.
/// </summary>
[Required(ErrorMessage = "Xác nhận mật khẩu là bắt buộc / Confirm password is required")]
[Compare(nameof(Password), ErrorMessage = "Mật khẩu không khớp / Passwords do not match")]
public string ConfirmPassword { get; set; } = string.Empty;
/// <summary>
/// EN: User display name.
/// VI: Tên hiển thị của user.
/// </summary>
[Required(ErrorMessage = "Tên là bắt buộc / Name is required")]
[StringLength(50, MinimumLength = 2, ErrorMessage = "Tên phải từ 2-50 ký tự / Name must be 2-50 chars")]
public string DisplayName { get; set; } = string.Empty;
/// <summary>
/// EN: Accept terms of service.
/// VI: Chấp nhận điều khoản dịch vụ.
/// </summary>
[Range(typeof(bool), "true", "true", ErrorMessage = "Bạn phải chấp nhận Điều khoản dịch vụ / You must accept the Terms of Service")]
public bool AcceptTerms { get; set; }
}
/// <summary>
/// EN: User login DTO.
/// VI: DTO đăng nhập user.
/// </summary>
public class LoginDto
{
/// <summary>
/// EN: User email address.
/// VI: Địa chỉ email user.
/// </summary>
[Required(ErrorMessage = "Email là bắt buộc / Email is required")]
[EmailAddress(ErrorMessage = "Email không hợp lệ / Invalid email format")]
public string Email { get; set; } = string.Empty;
/// <summary>
/// EN: User password.
/// VI: Mật khẩu user.
/// </summary>
[Required(ErrorMessage = "Mật khẩu là bắt buộc / Password is required")]
public string Password { get; set; } = string.Empty;
/// <summary>
/// EN: Remember me option.
/// VI: Tùy chọn ghi nhớ đăng nhập.
/// </summary>
public bool RememberMe { get; set; }
}
/// <summary>
/// EN: User profile DTO.
/// VI: DTO hồ sơ user.
/// </summary>
public class UserProfileDto
{
public Guid Id { get; set; }
public string Email { get; set; } = string.Empty;
public string DisplayName { get; set; } = string.Empty;
public string? AvatarUrl { get; set; }
public DateTime CreatedAt { get; set; }
public bool EmailVerified { get; set; }
}

View File

@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>