diff --git a/apps/web-client-base-net/Dockerfile b/apps/web-client-base-net/Dockerfile new file mode 100644 index 00000000..567f5364 --- /dev/null +++ b/apps/web-client-base-net/Dockerfile @@ -0,0 +1,66 @@ +# ═══════════════════════════════════════════════════════════════════════════════ +# WebClientBase Dockerfile +# EN: Multi-stage build for Blazor WebAssembly Hosted +# VI: Multi-stage build cho Blazor WebAssembly Hosted +# ═══════════════════════════════════════════════════════════════════════════════ + +# ═══════════════════════════════════════════════════════════════════════════════ +# Stage 1: Build +# ═══════════════════════════════════════════════════════════════════════════════ +FROM mcr.microsoft.com/dotnet/sdk:10.0-alpine AS build +WORKDIR /src + +# EN: Copy solution and project files for layer caching +# VI: Copy solution và project files để cache layers +COPY WebClientBase.slnx ./ +COPY src/WebClientBase.Shared/WebClientBase.Shared.csproj ./src/WebClientBase.Shared/ +COPY src/WebClientBase.Client/WebClientBase.Client.csproj ./src/WebClientBase.Client/ +COPY src/WebClientBase.Server/WebClientBase.Server.csproj ./src/WebClientBase.Server/ + +# EN: Restore dependencies +# VI: Restore dependencies +RUN dotnet restore + +# EN: Copy source code +# VI: Copy source code +COPY . . + +# EN: Build and publish +# VI: Build và publish +RUN dotnet publish src/WebClientBase.Server/WebClientBase.Server.csproj \ + -c Release \ + -o /app/publish \ + --no-restore + +# ═══════════════════════════════════════════════════════════════════════════════ +# Stage 2: Runtime +# ═══════════════════════════════════════════════════════════════════════════════ +FROM mcr.microsoft.com/dotnet/aspnet:10.0-alpine AS runtime +WORKDIR /app + +# EN: Create non-root user for security +# VI: Tạo user không phải root để bảo mật +RUN adduser -D -u 1000 appuser && chown -R appuser /app +USER appuser + +# EN: Copy published output +# VI: Copy output đã publish +COPY --from=build /app/publish . + +# EN: Expose port +# VI: Expose port +EXPOSE 8080 + +# EN: Health check +# VI: Health check +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1 + +# EN: Set environment variables +# VI: Thiết lập biến môi trường +ENV ASPNETCORE_URLS=http://+:8080 +ENV ASPNETCORE_ENVIRONMENT=Production + +# EN: Run the application +# VI: Chạy ứng dụng +ENTRYPOINT ["dotnet", "WebClientBase.Server.dll"] diff --git a/apps/web-client-base-net/README.md b/apps/web-client-base-net/README.md new file mode 100644 index 00000000..b75aaebd --- /dev/null +++ b/apps/web-client-base-net/README.md @@ -0,0 +1,116 @@ +# WebClientBase - Blazor Web App .NET 10 + +Base frontend web application cho GoodGo Platform được xây dựng với Blazor WebAssembly Hosted. + +## Features / Tính năng + +- **Blazor WebAssembly Hosted** - Client chạy trong browser, Server host API +- **Shared Library Pattern** - DTOs và Validation được chia sẻ giữa Client và Server +- **Data Annotations Validation** - Viết 1 lần, dùng chung cả 2 đầu +- **[ApiController] Auto-Validation** - Server tự động validate, không cần `if (!ModelState.IsValid)` +- **Dark/Light Mode** - CSS variables với theme toggle +- **Glassmorphism UI** - Modern design với backdrop blur và shadows + +## Tech Stack + +| Layer | Technology | +|-------|------------| +| Client | Blazor WebAssembly (.NET 10) | +| Server | ASP.NET Core Web API (.NET 10) | +| Shared | Class Library với Data Annotations | +| Styling | CSS Variables, Dark Mode | + +## Project Structure / Cấu trúc dự án + +``` +web-client-base-net/ +├── WebClientBase.slnx # Solution file +├── src/ +│ ├── WebClientBase.Client/ # Blazor WebAssembly +│ │ ├── Layout/ # MainLayout, NavMenu +│ │ ├── Pages/ # Razor pages (Products, Auth) +│ │ └── wwwroot/css/ # Design System CSS +│ ├── WebClientBase.Server/ # ASP.NET Core Host +│ │ └── Controllers/ # API Controllers +│ └── WebClientBase.Shared/ # Shared Library +│ └── DTOs/ # Data Transfer Objects +└── Dockerfile +``` + +## Getting Started / Bắt đầu + +```bash +# Navigate to project +cd apps/web-client-base-net + +# Restore packages +dotnet restore + +# Run Server (hosts both API and Blazor client) +dotnet run --project src/WebClientBase.Server + +# Open browser +# https://localhost:5001 hoặc http://localhost:5000 +``` + +## Shared Validation Pattern / Mẫu Validation Chia Sẻ + +### 1. Define DTO with Validation (một lần) +```csharp +// WebClientBase.Shared/DTOs/ProductDto.cs +public class ProductDto +{ + [Required(ErrorMessage = "Tên là bắt buộc / Name is required")] + [StringLength(100, MinimumLength = 3)] + public string Name { get; set; } = string.Empty; + + [Range(0.01, 1_000_000)] + public decimal Price { get; set; } +} +``` + +### 2. Server (Auto-validation với [ApiController]) +```csharp +[ApiController] +[Route("api/[controller]")] +public class ProductsController : ControllerBase +{ + [HttpPost] + public IActionResult Create(ProductDto request) + { + // Không cần if (!ModelState.IsValid) - [ApiController] đã làm hộ + return Ok(ApiResponse.Ok(request)); + } +} +``` + +### 3. Client (DataAnnotationsValidator) +```razor + + + + + + + + + +``` + +## API Endpoints + +| Method | Endpoint | Description | +|--------|----------|-------------| +| GET | `/api/products` | Get all products | +| POST | `/api/products` | Create product | +| PUT | `/api/products` | Update product | +| POST | `/api/auth/register` | Register user | +| POST | `/api/auth/login` | Login | +| GET | `/api/auth/profile/{id}` | Get profile | +| GET | `/health` | Health check | + +## Related Skills + +- [React Enterprise Architect](../../.agent/skills/react-enterprise-architect/SKILL.md) +- [Tailwind Design System](../../.agent/skills/tailwind-design-system/SKILL.md) +- [Project Rules](../../.agent/skills/project-rules/SKILL.md) diff --git a/apps/web-client-base-net/src/WebClientBase.Client/Layout/MainLayout.razor b/apps/web-client-base-net/src/WebClientBase.Client/Layout/MainLayout.razor index 76eb7252..ecd27001 100644 --- a/apps/web-client-base-net/src/WebClientBase.Client/Layout/MainLayout.razor +++ b/apps/web-client-base-net/src/WebClientBase.Client/Layout/MainLayout.razor @@ -1,16 +1,60 @@ @inherits LayoutComponentBase -
- + +@code { + private string currentTheme = "light"; + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + // EN: Load saved theme from localStorage + // VI: Tải theme đã lưu từ localStorage + var savedTheme = await JS.InvokeAsync("localStorage.getItem", "theme"); + if (!string.IsNullOrEmpty(savedTheme)) + { + currentTheme = savedTheme; + StateHasChanged(); + } + } + } + + private async Task ToggleTheme() + { + currentTheme = currentTheme == "dark" ? "light" : "dark"; + + // EN: Save theme to localStorage + // VI: Lưu theme vào localStorage + await JS.InvokeVoidAsync("localStorage.setItem", "theme", currentTheme); + } +} diff --git a/apps/web-client-base-net/src/WebClientBase.Client/Layout/NavMenu.razor b/apps/web-client-base-net/src/WebClientBase.Client/Layout/NavMenu.razor index fb102416..b11f212f 100644 --- a/apps/web-client-base-net/src/WebClientBase.Client/Layout/NavMenu.razor +++ b/apps/web-client-base-net/src/WebClientBase.Client/Layout/NavMenu.razor @@ -1,39 +1,30 @@ -