docs: add session log for 2026-05-13 (guest browsing + top-seller landing sections)
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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<bool> RunAsync(Func<Task> 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<GuestActionGate>();
|
||||
```
|
||||
|
||||
### ۲. `ProductService.cs`
|
||||
|
||||
```csharp
|
||||
public Task<ProductListResult> GetTopSellingAsync(int count = 6)
|
||||
=> GetProductsPagedAsync(sortBy: "SaleCount desc", page: 1, pageSize: count);
|
||||
```
|
||||
|
||||
### ۳. `DiscountProductService.cs`
|
||||
|
||||
```csharp
|
||||
// پارامتر جدید به GetProductsAsync اضافه شد
|
||||
public async Task<DiscountProductListResult> GetProductsAsync(
|
||||
..., string? sortBy = null)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(sortBy))
|
||||
request.SortBy = sortBy;
|
||||
...
|
||||
}
|
||||
|
||||
public Task<DiscountProductListResult> 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)
|
||||
```
|
||||
Reference in New Issue
Block a user