From 4218d08597ef84f6ecad6bff13447d1242f01840 Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Wed, 13 May 2026 19:50:15 +0330 Subject: [PATCH] docs: add session log for 2026-05-13 (guest browsing + top-seller landing sections) Co-authored-by: Cursor --- technical/TECH-08-SESSION-LOG-20260513.md | 303 ++++++++++++++++++++++ 1 file changed, 303 insertions(+) create mode 100644 technical/TECH-08-SESSION-LOG-20260513.md diff --git a/technical/TECH-08-SESSION-LOG-20260513.md b/technical/TECH-08-SESSION-LOG-20260513.md new file mode 100644 index 0000000..b081c76 --- /dev/null +++ b/technical/TECH-08-SESSION-LOG-20260513.md @@ -0,0 +1,303 @@ +# TECH-08 — لاگ Session 1404/02/23 (2026-05-13) + +> نوع سند: **گزارش کار** +> تاریخ: ۱۴۰۵/۰۲/۲۳ +> مرتبط با: CMS · FrontOffice +> کامیت CMS: `683ed37` (branch: `kub-stage`) +> کامیت FrontOffice: `231da2c` (branch: `kub-stage`) + +--- + +## فهرست مطالب + +1. [هدف و خلاصه](#هدف-و-خلاصه) +2. [تغییرات CMS (Backend)](#تغییرات-cms-backend) +3. [تغییرات FrontOffice](#تغییرات-frontoffice) +4. [معماری GuestActionGate](#معماری-guestactiongate) +5. [فلوچارت تجربه کاربر](#فلوچارت-تجربه-کاربر) +6. [فایل‌های تغییر یافته](#فایلهای-تغییر-یافته) + +--- + +## هدف و خلاصه + +هدف این session: + +1. **نمایش ۶ محصول پرفروش معمولی + ۶ محصول پرفروش فروشگاه اعتباری** در لندینگ پیج FrontOffice، زیر هدر اصلی (۳ محصول در هر ردیف، دو section مجزا). +2. **دسترسی guest** (کاربر بدون لاگین) به مرور محصولات برای پرزنت به مشتریان بالقوه. +3. **Hybrid auth flow**: کاربر guest محصولات را می‌بیند؛ اگر روی "افزودن به سبد" کلیک کرد، مودال لاگین باز می‌شود و پس از ورود موفق، عمل به صورت خودکار انجام می‌شود. + +--- + +## تغییرات CMS (Backend) + +### ۱. `discountproduct.proto` + +```proto +// اضافه شده به GetDiscountProductsRequest +google.protobuf.StringValue sort_by = 9; + +// اضافه شده به DiscountProductDto +int32 sale_count = 12; +``` + +**چرا:** برای واکشی پرفروش‌ترین محصولات فروشگاه اعتباری باید امکان sort بر اساس `sale_count` وجود داشته باشد. قبلاً این فیلد در DTO برگردانده نمی‌شد. + +### ۲. `CMSMicroservice.Protobuf.csproj` + +نسخه از `0.0.195` به `0.0.196` بالا رفت تا پکیج NuGet جدید publish شود. + +### ۳. `GetDiscountProductsQuery.cs` + +```csharp +public string? SortBy { get; set; } +``` + +### ۴. `GetDiscountProductsQueryHandler.cs` + +```csharp +// قبل: همیشه OrderByDescending(p => p.Created) +// بعد: dynamic sort با fallback +if (!string.IsNullOrEmpty(request.SortBy)) + query = query.ApplyOrder(request.SortBy); +else + query = query.OrderByDescending(p => p.Created); + +// و در SELECT: +SaleCount = p.SaleCount, +``` + +از extension method موجود `ApplyOrder` (کتابخانه `System.Linq.Dynamic.Core`) استفاده شد تا نیازی به تغییر جداگانه نباشد. + +### ۵. `DiscountProductProfile.cs` (Mapster) + +```csharp +// Request mapping +.Map(dest => dest.SortBy, src => string.IsNullOrEmpty(src.SortBy) ? null : src.SortBy) + +// Response mapping +SaleCount = p.SaleCount, +``` + +--- + +## تغییرات FrontOffice + +### ۱. `GuestActionGate.cs` (فایل جدید) + +``` +FrontOffice.Main/Utilities/GuestActionGate.cs +``` + +سرویس utility جدید که هر action نیازمند لاگین را wrap می‌کند: + +```csharp +public async Task RunAsync(Func action) +{ + if (await _authService.IsAuthenticatedAsync()) + { + await action(); + return true; + } + await _authDialogService.ShowAuthDialogAsync(); + if (await _authService.IsAuthenticatedAsync()) + { + await action(); + return true; + } + return false; +} +``` + +در `ConfigureServices.cs` به صورت Scoped ثبت شد: + +```csharp +services.AddScoped(); +``` + +### ۲. `ProductService.cs` + +```csharp +public Task GetTopSellingAsync(int count = 6) + => GetProductsPagedAsync(sortBy: "SaleCount desc", page: 1, pageSize: count); +``` + +### ۳. `DiscountProductService.cs` + +```csharp +// پارامتر جدید به GetProductsAsync اضافه شد +public async Task GetProductsAsync( + ..., string? sortBy = null) +{ + if (!string.IsNullOrWhiteSpace(sortBy)) + request.SortBy = sortBy; + ... +} + +public Task GetTopSellingAsync(int count = 6) + => GetProductsAsync(page: 1, pageSize: count, sortBy: "SaleCount desc"); +``` + +### ۴. `Index.razor` و `Index.razor.cs` + +دو section جدید در لندینگ پیج زیر hero اضافه شد: + +**Section 1 — محصولات پرفروش معمولی:** +- عنوان: "محصولات پرفروش" +- ۶ کارت (۳ در هر ردیف با MudGrid) +- هر کارت: تصویر، نام، قیمت با VAT، دکمه "افزودن به سبد" +- دکمه "بیشتر" → `/products` + +**Section 2 — محصولات پرفروش فروشگاه اعتباری:** +- عنوان: "فروشگاه اعتباری" +- ۶ کارت (۳ در هر ردیف) +- هر کارت: تصویر، نام، قیمت، درصد تخفیف +- دکمه "بیشتر" → `/discount-store` + +**Loading state:** در حین بارگذاری یک spinner نشان داده می‌شود و سپس section‌ها fade-in می‌شوند. + +**Data loading (parallel):** +```csharp +var topRegTask = ProductService.GetTopSellingAsync(6); +var topDiscTask = DiscountProductService.GetTopSellingAsync(6); +var featuredPostsTask = BlogPostService.GetFeaturedPostsAsync(2); +await Task.WhenAll(topRegTask, topDiscTask, featuredPostsTask); +``` + +**Cart actions با GuestActionGate:** +```csharp +private async Task AddRegularToCart(Product p) + => await GuestGate.RunAsync(() => Cart.Add(p, 1)); + +private async Task AddDiscountToCart(DiscountProductCard p) + => await GuestGate.RunAsync(() => DiscountCart.AddAsync(p.Id)); +``` + +### ۵. Hybridize کردن صفحات موجود + +#### صفحات لیست و جزئیات محصول (GuestActionGate): + +| فایل | تغییر | +|------|-------| +| `Store/Products.razor.cs` | `AddToCart` → `GuestGate.RunAsync(...)` | +| `Store/ProductDetail.razor.cs` | `AddToCart` و `RemoveFromCart` → `GuestGate.RunAsync(...)` | +| `DiscountStore/Products.razor.cs` | `AddToCart` → `GuestGate.RunAsync(...)` | +| `DiscountStore/ProductDetail.razor.cs` | `AddToCart` → `GuestGate.RunAsync(...)` | + +#### صفحات Cart و Checkout (Soft Auth Gate): + +```csharp +protected override async Task OnInitializedAsync() +{ + if (!await AuthService.IsAuthenticatedAsync()) + { + await AuthDialogService.ShowAuthDialogAsync(); + } + // ادامه بارگذاری... +} +``` + +این pattern روی: +- `Store/Cart.razor.cs` +- `Store/CheckoutSummary.razor.cs` +- `DiscountStore/Cart.razor.cs` +- `DiscountStore/Checkout.razor.cs` + +اعمال شد. اگر guest مستقیماً وارد سبد خرید شود، مودال لاگین نشان داده می‌شود. + +### ۶. `MembershipPage.razor` (fix متنی) + +```diff +- شارژ ۵۶ میلیون تومان کیف پول فروشگاه اعتباری ++ شارژ برابر ارزش پکیج فعال در کیف پول فروشگاه اعتباری +``` + +متن hardcode‌شده با مقدار دینامیک جایگزین شد. + +--- + +## معماری GuestActionGate + +``` +کاربر کلیک می‌کند + │ + ▼ +GuestActionGate.RunAsync(action) + │ + ├─► آیا لاگین است؟ ──YES──► action() اجرا می‌شود ✅ + │ + NO + │ + ▼ +AuthDialogService.ShowAuthDialogAsync() +(مودال OTP باز می‌شود) + │ + ├─► آیا لاگین شد؟ ──YES──► action() اجرا می‌شود ✅ + │ + NO (بستن مودال) + │ + ▼ + return false (هیچ اتفاقی نمی‌افتد) ❌ +``` + +این pattern **defense-in-depth** است: `CartService.Add` هم به تنهایی چک `IsAuthenticatedAsync` دارد؛ `GuestActionGate` لایه UX روی آن اضافه می‌کند. + +--- + +## فلوچارت تجربه کاربر + +``` +کاربر وارد لندینگ پیج می‌شود (بدون لاگین) + │ + ├─► ۶ محصول پرفروش معمولی نمایش داده می‌شود + ├─► ۶ محصول پرفروش اعتباری نمایش داده می‌شود + │ + ├─► "بیشتر" کلیک → /products یا /discount-store + │ (صفحات لیست کامل، بدون لاگین قابل مرور) + │ + ├─► روی محصول کلیک → صفحه جزئیات + │ (بدون لاگین قابل مشاهده) + │ + └─► "افزودن به سبد" کلیک + │ + ▼ + مودال لاگین (OTP) + │ + ├─► ورود موفق → محصول به سبد اضافه می‌شود ✅ + └─► بستن مودال → هیچ اتفاقی نمی‌افتد +``` + +--- + +## فایل‌های تغییر یافته + +### CMS — کامیت `683ed37` + +``` +src/CMSMicroservice.Protobuf/Protos/discountproduct.proto (+2) +src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj (~2) +src/CMSMicroservice.Application/DiscountShopCQ/Queries/ + GetDiscountProducts/GetDiscountProductsQuery.cs (+1) + GetDiscountProducts/GetDiscountProductsQueryHandler.cs (+7 -3) +src/CMSMicroservice.WebApi/Common/Mappings/DiscountProductProfile.cs (+3) +``` + +### FrontOffice — کامیت `231da2c` + +``` +src/FrontOffice.Main/Utilities/GuestActionGate.cs (NEW +42) +src/FrontOffice.Main/ConfigureServices.cs (+1) +src/FrontOffice.Main/Utilities/ProductService.cs (+3) +src/FrontOffice.Main/Utilities/DiscountProductService.cs (+8) +src/FrontOffice.Main/Pages/Index.razor (+~180) +src/FrontOffice.Main/Pages/Index.razor.cs (+45) +src/FrontOffice.Main/Pages/Store/Products.razor.cs (+5) +src/FrontOffice.Main/Pages/Store/ProductDetail.razor.cs (+5) +src/FrontOffice.Main/Pages/Store/Cart.razor.cs (+8) +src/FrontOffice.Main/Pages/Store/CheckoutSummary.razor.cs (+8) +src/FrontOffice.Main/Pages/DiscountStore/Products.razor.cs (+5) +src/FrontOffice.Main/Pages/DiscountStore/ProductDetail.razor.cs (+5) +src/FrontOffice.Main/Pages/DiscountStore/Cart.razor.cs (+8) +src/FrontOffice.Main/Pages/DiscountStore/Checkout.razor.cs (+8) +src/FrontOffice.Main/Pages/Club/MembershipPage.razor (~1) +```