Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Successful in 6m2s

This commit is contained in:
masoodafar-web
2026-02-21 20:38:23 +03:30
11 changed files with 372 additions and 859 deletions
+2 -310
View File
@@ -1,311 +1,3 @@
# 📋 تاریخچه تغییرات FrontOffice — کارا بازار سلامت
# Docs moved to totalDoc
> **آخرین بروزرسانی**: اسفند ۱۴۰۴ (فوریه ۲۰۲۶)
> **فریمورک**: Blazor Server + MudBlazor 8.14 + .NET 9
---
## 🔖 نسخه ۲.۷.۰ — اسفند ۱۴۰۴ (February 17, 2026)
### ۱. اجبار تخفیف ۱۰۰٪ و حذف اسلایدر
- **حذف** `MudSlider` و `MudNumericField` از `Checkout.razor` — کاربر دیگه درصد تخفیف انتخاب نمی‌کنه
- **حذف** کامل بخش نمایش موجودی اعتباری
- بکند همیشه حداکثر تخفیف (`MaxDiscountPercent`) رو اعمال می‌کنه
### ۲. رفع متن Badge تخفیف
- **مشکل:** Badge روی محصولات "۱۰۰٪ اعتباری" نشون می‌داد — گمراه‌کننده
- **رفع:** نمایش درصد واقعی محصول (مثلاً "۳۰٪ تخفیف")
- **فایل‌ها:** `Products.razor`, `Cart.razor`, `ProductDetail.razor`
### ۳. نمایش جدول مالیات (VAT) در Checkout
- جمع کل، تخفیف، مبلغ پس از تخفیف، مالیات ۹٪، مبلغ قابل پرداخت
- استفاده از `VatCalculator` برای محاسبه VAT
### ۴. نمایش وضعیت پرداخت در سفارشات
- **فیلد جدید** `payment_status` در proto `discountorder.proto` (v0.0.179)
- **صفحه Orders:** نمایش Chip رنگی بر اساس PaymentStatus (موفق/ناموفق/در انتظار)
- **صفحه OrderDetail:** Alert برای سفارشات ناموفق + دکمه بازگشت + جزئیات وضعیت
- **Helper متدها:** `GetPaymentStatusText()`, `GetPaymentStatusColor()`, `GetDeliveryStatusText()`
### ۵. فیکس مپینگ PaymentStatus و DeliveryStatus
- **مشکل:** Domain enum مقادیر متفاوتی از Proto داشت — مستقیم cast می‌شد
- **رفع:** `MapPaymentStatus()` و `MapDeliveryStatus()` در `DiscountOrderService.cs` (WebApi)
- سفارشات ناموفق حالا `DeliveryStatus=Cancelled` دارن (بجای "در حال پردازش")
### ۶. فیکس Proto به NuGet v0.0.179
- `FrontOffice.Main.csproj`: `PackageReference` به `Foursat.CMSMicroservice.Protobuf` v0.0.179
---
## 🔖 نسخه ۲.۶.۰ — بهمن ۱۴۰۴ (February 16, 2026)
### ۱. درگاه پرداخت زرین‌پال
- **یکپارچه‌سازی ZarinPal** — پرداخت مستقیم بدون PYMS واسط
- صفحه checkout فروشگاه اعتباری: ریدایرکت به ZarinPal → callback → تأیید
- صفحه checkout فروشگاه عادی: همان جریان
- پشتیبانی از sandbox و production
### ۲. جدول PaymentTransaction
- **Entity جدید** `PaymentTransaction` — ذخیره جزئیات سطح درگاه (Authority, CardPan, CardHash, RefId)
- جدا از جدول Transaction اصلی
- Migration: `AddPaymentTransactionTable`
### ۳. فیکس نمایش وضعیت پرداخت
- **مشکل:** سفارشات اعتباری «در انتظار پرداخت» نشان می‌دادند حتی بعد از پرداخت موفق
- **علت:** Mapster نمی‌تونست `PaymentStatus` (enum) رو به `payment_completed` (bool) مپ کنه
- **رفع:** مپینگ دستی در `DiscountOrderService`
### ۴. فیکس DeliveryStatus
- **مشکل:** فروشگاه اعتباری بعد از پرداخت `InTransit` ست می‌کرد
- **رفع:** تغییر به `Pending` — ادمین باید وضعیت پست رو مشخص کنه
### ۵. تغییر به NuGet Package (Docker build fix)
- `ProjectReference` به CMS Protobuf → `PackageReference` (v0.0.178)
- Docker build context فقط `src/` داره → مسیر `../../../CMS` قابل دسترسی نیست
---
## 🔖 نسخه ۲.۵.۰ — بهمن ۱۴۰۴
### ۱. محافظت صفحات نیازمند احراز هویت (Auth Guard)
**فایل‌های تغییریافته:**
- `Shared/MainLayout.razor.cs`
**شرح:**
قبلاً هیچ محافظتی در سطح مسیریابی وجود نداشت — کاربر غیرلاگین می‌توانست مستقیماً به `/profile/*`، `/commission/*`، `/cart` و... دسترسی پیدا کند.
**تغییرات:**
- متد `EnforceAuthGuardAsync()` اضافه شد — در هر تغییر مسیر و اولین بار رندر اجرا می‌شود
- متد `IsProtectedRoute(path)` مسیرهای محافظت‌شده را تشخیص می‌دهد
- کاربر غیرلاگین → ریدایرکت به `/` (صفحه اصلی)
**مسیرهای محافظت‌شده:**
| گروه | مسیرها |
|---|---|
| پروفایل | `/profile/*` |
| کمیسیون | `/commission/*` |
| شبکه | `/network/*` |
| باشگاه | `/club/*` |
| سبد خرید | `/cart`, `/checkout*`, `/orders`, `/order/*`, `/order-tracking/*` |
| پکیج‌ها | `/my-packages` |
| دروازه | `/my-orders`, `/my-cart` |
| فروشگاه اعتباری | `/discount-store/cart`, `/discount-store/checkout`, `/discount-store/orders`, `/discount-store/order/*` |
**مسیرهای عمومی:**
`/`, `/register`, `/about`, `/faq`, `/contact`, `/blog/*`, `/packages`, `/package/*`, `/products`, `/product/*`, `/categories`, `/stores`, `/discount-store`, `/discount-store/product/*`
---
### ۲. استخراج کامپوننت PhoneVerifyForm
**فایل‌های جدید:**
- `Shared/PhoneVerifyForm.razor`
- `Shared/PhoneVerifyForm.razor.cs`
**فایل‌های تغییریافته:**
- `Shared/AuthDialog.razor`
- `Shared/AuthDialog.razor.cs`
**شرح:**
فرم تلفن + تایید OTP + کپچا که قبلاً به‌صورت `RenderFragment` با `__builder` مستقیم در `AuthDialog` نوشته شده بود، به کامپوننت مستقل `PhoneVerifyForm` استخراج شد.
**ساختار قبل:**
```
AuthDialog.razor
└── @code { PhoneOrVerifyContent() => __builder => { ... } } ← RenderFragment پیچیده
```
**ساختار بعد:**
```
AuthDialog.razor
└── <PhoneVerifyForm @ref="_phoneVerifyForm" ... /> ← کامپوننت مستقل
PhoneVerifyForm.razor ← مارکاپ فرم
PhoneVerifyForm.razor.cs ← پارامترها و فرم رف‌ها
```
**پارامترهای PhoneVerifyForm:**
| پارامتر | نوع | توضیح |
|---|---|---|
| `CurrentStep` | `AuthStep` | مرحله فعلی (Phone / Verify) |
| `PhoneRequest` | `CreateNewOtpTokenRequest` | مدل فرم تلفن |
| `VerifyRequest` | `VerifyOtpTokenRequest` | مدل فرم تایید |
| `CaptchaCode` | `string?` | کد کپچا نمایش‌داده‌شده |
| `CaptchaInput` / `CaptchaInputChanged` | `string?` + `EventCallback` | ورودی کپچا (two-way) |
| `OnRefreshCaptcha` | `EventCallback` | رفرش کپچا |
| `IsBusy` | `bool` | وضعیت بارگذاری |
| `ErrorMessage` / `InfoMessage` | `string?` | پیام‌های خطا/اطلاع |
| `PhoneNumber` | `string?` | شماره تایید‌شده |
| `ResendRemaining` | `int` | ثانیه تا ارسال مجدد |
| `OnChangePhone` / `OnResendOtp` | `EventCallback` | اکشن‌های تایید |
**نکته مهم — Two-way binding کپچا:**
فیلد کپچا با `Value` + `ValueChanged` بایند شده (نه `@bind-Value`) تا مقدار تایپ‌شده از فرزند به والد برگردد:
```razor
<MudTextField Value="CaptchaInput"
ValueChanged="@((string v) => CaptchaInputChanged.InvokeAsync(v))" ... />
```
**دسترسی به فرم‌ها از والد:**
```csharp
// AuthDialog.razor.cs
var phoneForm = _phoneVerifyForm?.GetPhoneForm();
var verifyForm = _phoneVerifyForm?.GetVerifyForm();
```
---
### ۳. بهبود لایوت مدال ورود (AuthDialog)
**فایل‌های تغییریافته:**
- `Shared/AuthDialog.razor`
- `Utilities/AuthDialogService.cs`
- `wwwroot/css/site.css`
**مشکلات قبلی:**
- دیالوگ روی موبایل `FullScreen` بود → فضای خالی بزرگ بین فرم و دکمه‌ها
- `TitleContent` و `DialogActions` جدا → گپ عمودی
- ردیف کپچا با `MudStack Row="true"` → آیتم‌ها عمودی رندر می‌شدند
**تغییرات:**
1. **حذف FullScreen**: `AuthDialogService` حالا همیشه `MaxWidth.ExtraSmall, FullWidth=true, CloseButton=true`
2. **ادغام محتوا**: آواتار + عنوان + فرم + دکمه‌ها همه داخل `DialogContent` → بدون `TitleContent` و `DialogActions`
3. **کلاس `auth-dialog-wrapper`**: CSS با `.auth-dialog-wrapper .mud-dialog-title { display: none; }` عنوان پیش‌فرض دیالوگ رو مخفی می‌کنه
4. **ردیف کپچا**: از `MudStack Row` به `div.captcha-row` با CSS flex اختصاصی
5. **حالت Inline**: بدون تغییر ساختاری — فقط از `PhoneVerifyForm` استفاده می‌کنه
**CSS جدید:**
```css
.auth-dialog-wrapper .mud-dialog-title { display: none; }
.auth-dialog-wrapper .mud-dialog-content { padding-bottom: 24px !important; }
.auth-dialog-content { max-width: 400px; margin: 0 auto; }
.captcha-row { display: flex; align-items: center; gap: 10px; }
.captcha-row > .mud-input-control { flex: 1 1 0; min-width: 0; }
.captcha-row > .mud-paper { flex: 0 0 auto; }
.captcha-row > .mud-button-root { flex: 0 0 auto; }
```
---
### ۴. صفحه لندینگ — حذف /pricing و اضافه بنر بلاگ
**فایل‌های تغییریافته:**
- `Pages/Index.razor`
- `wwwroot/css/site.css`
**تغییرات:**
1. دکمه هیرو «مشاهده پکیج‌ها» → **«آخرین اخبار»** با لینک `/blog`
2. **بنر جدیدترین مطلب** بین هیرو و «سه گام تا شروع» اضافه شد
- تصویر بندانگشتی + عنوان + خلاصه + آیکون شیشه‌ای
- هاور: `translateY(-2px)` + سایه بزرگ‌تر
**CSS جدید:**
```css
.landing-blog-banner { border: 1px solid var(--mud-palette-divider); transition: ... }
.landing-blog-banner:hover { box-shadow: var(--mud-elevation-4); transform: translateY(-2px); }
.landing-blog-thumb { width: 80px; height: 80px; border-radius: 12px; }
.landing-blog-title { -webkit-line-clamp: 1; font-weight: 600; }
.landing-blog-summary { -webkit-line-clamp: 1; }
```
---
### ۵. افزایش ارتفاع تکست‌باکس‌ها (Global)
**فایل تغییریافته:**
- `wwwroot/css/site.css`
**قبل:** `padding: 10px 14px`
**بعد:** `padding: 14px 14px` + `font-size: 1rem`
```css
.mud-input-outlined .mud-input-slot {
padding: 14px 14px !important;
font-size: 1rem;
}
```
تمام فیلدهای Outlined در کل اپلیکیشن بزرگ‌تر شدند.
---
### ۶. اصلاح RTL فیلدهای ورودی
**فایل تغییریافته:**
- `wwwroot/css/site.css`
**مشکل:** فیلدهای `type="tel"` به‌صورت پیش‌فرض مرورگر `direction: ltr` می‌گیرن — لیبل سمت راست ولی placeholder/cursor سمت چپ.
**اصلاح:**
```css
.mud-input-slot input,
.mud-input-slot textarea {
direction: rtl !important;
text-align: right !important;
}
```
---
### ۷. اصلاح captcha-box CSS
**فایل تغییریافته:**
- `wwwroot/css/site.css`
**قبل:**
```css
.captcha-box {
min-width: 120px; min-height: 56px;
background: linear-gradient(135deg, rgba(123,97,255,.12), rgba(255,140,189,.12));
}
```
**بعد:**
```css
.captcha-box {
min-width: 96px; min-height: 48px;
border-radius: var(--mud-default-borderradius);
background: rgba(99,102,241,.08);
border: 1px solid var(--mud-palette-divider);
}
```
---
## 📁 نقشه فایل‌ها
```
Shared/
├── AuthDialog.razor ← بازنویسی (کامپوننت PhoneVerifyForm جایگزین RenderFragment)
├── AuthDialog.razor.cs ← بروزرسانی (استفاده از _phoneVerifyForm)
├── PhoneVerifyForm.razor ← ✨ جدید (فرم تلفن + تایید + کپچا)
├── PhoneVerifyForm.razor.cs ← ✨ جدید (پارامترها و فرم رف‌ها)
├── MainLayout.razor.cs ← Auth Guard اضافه شد
Pages/
├── Index.razor ← /pricing → /blog + بنر بلاگ
Utilities/
├── AuthDialogService.cs ← حذف FullScreen، ثابت‌سازی سایز
wwwroot/css/
├── site.css ← captcha-row, auth-dialog, RTL fix, input height
```
---
## 🔍 وضعیت بیلد
| تاریخ | خطا | هشدار | توضیح |
|---|---|---|---|
| بهمن ۱۴۰۴ | **۰** | ۱۰۵ | MUD0002 warnings (بی‌خطر — مربوط به MudBlazor analyzer) |
See [totalDoc/INDEX.md](../../totalDoc/INDEX.md) for all documentation.
+2 -541
View File
@@ -1,542 +1,3 @@
# 🎨 پلن جامع یکپارچه‌سازی UI/UX — کارا بازار سلامت
# Docs moved to totalDoc
> **تاریخ**: بهمن ۱۴۰۴
> **وضعیت**: ✅ **تمام ۶ فاز + فاز ۷ (ناوبری و امنیت) تکمیل شد**
> **هدف**: یکپارچه‌سازی طراحی FrontOffice با ۲۰٪ تغییر UX و ۵۰٪ تغییر UI
> **فریمورک**: Blazor Server + MudBlazor 8.14
> **📋 تاریخچه تغییرات جزئی**: [CHANGELOG.md](CHANGELOG.md)
---
## 📊 خلاصه اجرایی
| شاخص | وضعیت قبل | وضعیت بعد |
|---|---|---|
| **صفحات کل** | ۴۱ صفحه + ۶ کامپوننت مشترک | ۴۱ صفحه + ۸ کامپوننت مشترک |
| **الگوی PageHeader** | ۱۹ صفحه از ۴۱ | ۳۲+ صفحه ✅ |
| **Inline Style سنگین** | ۱۱ صفحه 🔴 | ≤۲ صفحه (فقط gradient‌های تزئینی) ✅ |
| **Loading State** | ۲ الگوی متفاوت (Circular vs Linear) | `<LoadingState>` واحد ✅ |
| **Empty State** | ۴+ الگوی متناقض | `<EmptyState>` واحد ✅ |
| **Container Spacing** | ۶ الگوی متناقض | `py-6` استاندارد ✅ |
| **رنگ‌بندی** | رنگ‌های hardcoded در ۸+ صفحه | CSS Variable ✅ |
| **تم پالت** | Primary `#0380C0` (آبی ساده) | Indigo `#6366f1` + Full PaletteDark ✅ |
| **Elevation** | مخلوط ۲/۳/۴ | استاندارد ۰–۲ ✅ |
| **Build** | ۰ خطا | ۰ خطا ✅ |
### فازها
| فاز | عنوان | وضعیت |
|---|---|---|
| ۱ | زیرساخت دیزاین سیستم | ✅ تکمیل |
| ۲ | صفحات پروفایل | ✅ تکمیل |
| ۳ | صفحات تخصصی | ✅ تکمیل |
| ۴ | بهبود بصری فروشگاه | ✅ تکمیل |
| ۵ | صفحات عمومی | ✅ تکمیل |
| ۶ | پالیش و تست | ✅ تکمیل |
| ۷ | ناوبری، امنیت و بازسازی کامپوننت‌ها | ✅ تکمیل |
---
## 🔍 بخش ۱: تحلیل ضعف‌های جاری
### ۱.۱ ناسازگاری‌های ساختاری (Structural)
#### ❌ ۱.۱.۱ — PageHeader دوگانه
**مشکل**: نیمی از صفحات از `<PageHeader>` استفاده می‌کنند، نیم دیگر header دستی دارند.
| از `<PageHeader>` استفاده می‌کنند ✅ | Header دستی دارند ❌ |
|---|---|
| Store/* (۸ صفحه) | Profile/* (۷ صفحه) |
| DiscountStore/* (۶ صفحه) | Club/* (۲ صفحه) |
| Gateway/* (۳ صفحه) | Commission/* (۲ صفحه) |
| Package/* (۲ صفحه) | Network/* (۱ صفحه) |
| PackageDetail, Checkout | Blog/*, Index, About, Contact, FAQ |
**تأثیر**: ظاهر متفاوت دکمه بازگشت، فاصله‌بندی ناهماهنگ
#### ❌ ۱.۱.۲ — Container MaxWidth متناقض
```
MaxWidth.Large → اکثر صفحات
MaxWidth.Medium → Personal, Settings, OrderTracking, Blog/Post, Gateway/*
MaxWidth.Small → ChangePassword
ترکیبی (loading≠content) → OrderDetail, ProductDetail, PackageDetail
```
**تأثیر**: برخی صفحات پهن‌تر از حد نیاز هستند (مثلاً فرم‌های ساده با Large)
#### ❌ ۱.۱.۳ — Container Padding متناقض
```
py-6 → اکثریت (استاندارد)
py-8 → Package/Packages, Package/MyPackages
pa-2 pa-md-6 → Store/Products, DiscountStore/Products
py-6 py-md-10 → Gateway/*
py-4 py-md-6 → DiscountStore/ProductDetail
py-16 سکشنی → Index, About, Contact, FAQ
```
**قاعده پیشنهادی**: `py-6` برای صفحات داخلی، section-based برای صفحات عمومی
---
### ۱.۲ ناسازگاری‌های بصری (Visual)
#### ❌ ۱.۲.۱ — Inline Style سنگین (۱۱ صفحه)
| صفحه | نمونه مشکل‌دار |
|---|---|
| **Index.razor** | `Style="color:#fff; font-size:clamp(1.6rem,4.5vw,2.4rem);"` |
| **Profile/Index** | `Style="background:rgba(99,102,241,.12);"` |
| **Store/ProductDetail** | `style="width:100%;height:360px;background-image:url(...);"` |
| **PackageDetail** | `Style="background: radial-gradient(600px 280px..."` |
| **Checkout** | `Style="background: radial-gradient(..."` + `Elevation="4"` |
| **Blog/Index** | `Style="color:#fff; font-weight:700;"`, `Style="font-size:3.5rem;"` |
| **Blog/Post** | `Style="width:100%; height:100%;"`, `Style="font-size:4rem;"` |
| **About** | `Style="background: radial-gradient(...);"` |
| **WeeklyBalance** | `Style="background: linear-gradient(135deg, #e8f5e9..."` |
| **Gateway/**** | `Style="background:rgba(16,185,129,.12)..."`, `Style="color:#10b981;"` |
| **DiscountStore/ProductDetail** | `Style="background:rgba(16,185,129,.06)..."` |
#### ❌ ۱.۲.۲ — رنگ‌های Hardcoded
| رنگ | استفاده | باید باشد |
|---|---|---|
| `#10b981` | Gateway (سبز فروشگاه) | `var(--ds-color-store)` |
| `#ef4444` | Gateway (قرمز اعتباری) | `var(--ds-color-discount)` |
| `#6366f1`, `#818cf8`, `#a78bfa` | Hero/Banner gradients | `var(--ds-gradient-primary)` |
| `rgba(99,102,241,.12)` | Dashboard backgrounds | `var(--ds-primary-soft)` |
| `rgba(16,185,129,.06)` | Discount product highlights | `var(--ds-success-soft)` |
| `#e8f5e9`, `#c8e6c9` | WeeklyBalance stat cards | `var(--ds-success-gradient)` |
#### ❌ ۱.۲.۳ — Elevation ناهماهنگ
```
Elevation="0" → Blog cards (با border)
Elevation="1" → برخی صفحات
Elevation="2" → اکثر صفحات (استاندارد)
Elevation="3" → WeeklyBalance stat cards
Elevation="4" → Checkout.razor
```
**قاعده پیشنهادی**: `Elevation="0"` با `border` = کارت‌های اطلاعاتی، `Elevation="2"` = default
#### ❌ ۱.۲.۴ — Paper Rounding ناهماهنگ
```
rounded-lg (16px) → اکثریت
rounded-xl (20px) → Index, Profile/Index, Gateway, DiscountStore/ProductDetail
بدون class → design system !important → 12px
```
---
### ۱.۳ ناسازگاری‌های UX (تجربه کاربری)
#### ❌ ۱.۳.۱ — Loading State دوگانه
```
MudProgressCircular → Store/*, DiscountStore/*, Package/*, About, Blog, Addresses
MudProgressLinear → Club/*, Commission/*, Network/*, RegisterWizard
```
**مشکل**: کاربر دو تجربه مختلف «در حال بارگذاری» می‌بیند
#### ❌ ۱.۳.۲ — Empty State ناهماهنگ
```
MudAlert Severity.Info → Store/Orders, Categories, DiscountStore/Orders
Icon + Text + Button → Addresses, Package/MyPackages, PackageDetail
MudAlert Severity.Warning → WithdrawalRequests
Custom dashed-border paper → Blog/Index
```
#### ❌ ۱.۳.۳ — Routing Directive ناهماهنگ
```
@attribute [Route(RouteConstants...)] → ۳۹ صفحه ✅
@page "/categories" → Categories.razor ❌
@page "/blog/{Slug}" → Blog/Post.razor ❌
```
#### ❌ ۱.۳.۴ — PackageDetail Loading/Error بدون Container
Loading و Error state در `PackageDetail.razor` بدون `MudContainer` رندر می‌شوند → محتوا تمام‌عرض نمایش می‌یابد.
#### ❌ ۱.۳.۵ — تم فعلی کم‌رنگ
```csharp
// CustomMudTheme.cs فعلی
Primary = "#0380C0" // آبی ساده
// بدون Secondary، Tertiary، Info، Warning تعریف‌شده
// بدون PaletteDark
// بدون LayoutProperties
```
**مشکلات**:
- فقط Primary تعریف شده، بقیه رنگ‌ها default MudBlazor
- Dark mode بدون palette اختصاصی
- بدون `DefaultBorderRadius`، `AppbarHeight` و غیره
- تناقض بین Primary `#0380C0` و gradient‌های CSS با `#6366f1`
---
## 🎯 بخش ۲: معماری دیزاین سیستم هدف
### ۲.۱ سلسله‌مراتب صفحات
```
┌─────────────────────────────────────────────┐
│ MainLayout │
│ ├─ AppBar (fixed, transparent) │
│ ├─ MudMainContent │
│ │ ├─ [Public Pages] → Section-based │
│ │ │ (Index, About, Contact, FAQ, Blog) │
│ │ └─ [Internal Pages] → Container-based │
│ │ ├─ <PageHeader/> │
│ │ ├─ Content (MudStack/MudGrid) │
│ │ └─ </MudContainer> │
│ ├─ Footer (hidden on mobile) │
│ └─ BottomNav (mobile only) │
└─────────────────────────────────────────────┘
```
### ۲.۲ قواعد واحد (Single Source of Truth)
| قاعده | مقدار |
|---|---|
| **Container MaxWidth** | `Large` = لیست/گرید, `Medium` = فرم/جزئیات, `Small` = تک‌فرم ساده |
| **Container Spacing** | `py-6` صفحات داخلی, section-based صفحات عمومی |
| **Paper Elevation** | `0` با border = کارت اطلاعاتی, `2` = default |
| **Paper Rounding** | `rounded-lg` = default, `rounded-xl` = hero/banner |
| **Loading State** | `<LoadingState/>` component واحد |
| **Empty State** | `<EmptyState/>` component واحد |
| **Page Header** | `<PageHeader/>` در تمام صفحات داخلی |
| **Content Wrapper** | `MudStack Spacing="3"` بعد از PageHeader |
### ۲.۳ CSS Variables هدف
```css
:root {
/* ── Brand Colors ── */
--ds-brand-primary: #6366f1; /* Indigo — هویت اصلی */
--ds-brand-secondary: #8b5cf6; /* Purple */
--ds-brand-accent: #a78bfa; /* Light purple */
/* ── Semantic Colors ── */
--ds-color-store: #10b981; /* فروشگاه عادی */
--ds-color-discount: #ef4444; /* فروشگاه اعتباری */
--ds-color-success: #10b981;
--ds-color-warning: #f59e0b;
--ds-color-error: #ef4444;
--ds-color-info: #3b82f6;
/* ── Soft Backgrounds ── */
--ds-primary-soft: rgba(99,102,241,.08);
--ds-success-soft: rgba(16,185,129,.08);
--ds-error-soft: rgba(239,68,68,.08);
--ds-warning-soft: rgba(245,158,11,.08);
/* ── Gradients ── */
--ds-gradient-primary: linear-gradient(135deg, #6366f1 0%, #818cf8 50%, #a78bfa 100%);
--ds-gradient-store: linear-gradient(135deg, #10b981 0%, #34d399 100%);
--ds-gradient-discount: linear-gradient(135deg, #ef4444 0%, #f97316 50%, #f59e0b 100%);
--ds-gradient-success: linear-gradient(135deg, #d1fae5 0%, #a7f3d0 100%);
/* ── Spacing (existing) ── */
--ds-radius-sm: 8px;
--ds-radius-md: 12px;
--ds-radius-lg: 16px;
--ds-radius-xl: 20px;
--ds-transition: 0.2s ease;
--ds-shadow-sm: 0 1px 3px rgba(0,0,0,.06);
--ds-shadow-md: 0 4px 12px rgba(0,0,0,.08);
--ds-shadow-lg: 0 8px 24px rgba(0,0,0,.10);
}
```
---
## 🚀 بخش ۳: فازبندی اجرا
### 🔷 فاز ۱ — زیرساخت دیزاین سیستم (UI ~15%)
> **اولویت**: بالا | **ریسک**: پایین | **حجم**: ۶ فایل
| # | تسک | فایل | نوع تغییر |
|---|---|---|---|
| 1.1 | ارتقاء `CustomMudTheme.cs` — اضافه کردن PaletteDark، LayoutProperties، رنگ‌های Secondary/Tertiary/Info، تغییر Primary به `#6366f1` | `CustomMudTheme.cs` | UI |
| 1.2 | توسعه CSS Variables — اضافه کردن brand colors، semantic colors، soft backgrounds، gradients | `site.css` | UI |
| 1.3 | ساخت `<LoadingState>` component واحد | `Shared/LoadingState.razor` (جدید) | UX |
| 1.4 | ساخت `<EmptyState>` component واحد | `Shared/EmptyState.razor` (جدید) | UX |
| 1.5 | بهبود `<PageHeader>` — اضافه کردن آیکون، subtitle اختیاری | `Shared/PageHeader.razor` | UI |
| 1.6 | اضافه کردن `.page-container` CSS pattern | `site.css` | UI |
---
### 🔷 فاز ۲ — یکپارچه‌سازی صفحات Profile (UI ~10%, UX ~5%)
> **اولویت**: بالا | **ریسک**: پایین | **حجم**: ۹ فایل
| # | تسک | تغییرات |
|---|---|---|
| 2.1 | `Profile/Personal` → جایگزینی header دستی با `<PageHeader>` | UX |
| 2.2 | `Profile/Addresses``<PageHeader>` + `<LoadingState>` + `<EmptyState>` | UX |
| 2.3 | `Profile/Wallet``<PageHeader>` | UX |
| 2.4 | `Profile/Settings``<PageHeader>` | UX |
| 2.5 | `Profile/Tree``<PageHeader>` | UX |
| 2.6 | `Profile/WithdrawalRequests``<PageHeader>` + `<EmptyState>` | UX |
| 2.7 | `Profile/ChangePassword``<PageHeader>` | UX |
| 2.8 | `Profile/Index` (Dashboard) → حذف inline styles، استفاده از CSS Variables | UI |
| 2.9 | `Club/MembershipPage` + `Club/FeaturesPage``<PageHeader>` + `<LoadingState>` | UX |
---
### 🔷 فاز ۳ — یکپارچه‌سازی صفحات تخصصی (UI ~5%, UX ~5%)
> **اولویت**: متوسط | **ریسک**: پایین | **حجم**: ۵ فایل
| # | تسک | تغییرات |
|---|---|---|
| 3.1 | `Commission/Dashboard``<PageHeader>` + `<LoadingState>` | UX |
| 3.2 | `Commission/WeeklyBalance``<PageHeader>` + حذف inline gradient styles | UI + UX |
| 3.3 | `Network/NetworkStatistics``<PageHeader>` + `<LoadingState>` | UX |
| 3.4 | `PackageDetail` → wrap loading/error در `MudContainer` | UX bug fix |
| 3.5 | `Checkout` → Elevation=4→2، حذف inline radial-gradient | UI |
---
### 🔷 فاز ۴ — بهبود بصری فروشگاه‌ها (UI ~10%)
> **اولویت**: متوسط | **ریسک**: پایین | **حجم**: ۶ فایل
| # | تسک | تغییرات |
|---|---|---|
| 4.1 | `Store/Products` → حذف inline hero styles، استفاده از CSS class | UI |
| 4.2 | `Store/ProductDetail` → حذف inline image styles، ساخت `.product-image-main` CSS | UI |
| 4.3 | `DiscountStore/ProductDetail` → حذف hardcoded rgba، استفاده از `--ds-success-soft` | UI |
| 4.4 | `Gateway/*` (۳ صفحه) → حذف hardcoded `#10b981`/`#ef4444`، استفاده از `--ds-color-store`/`--ds-color-discount` | UI |
| 4.5 | یکسان‌سازی Elevation → `0` با border یا `2` | UI |
| 4.6 | یکسان‌سازی Container spacing → `py-6` | UI |
---
### 🔷 فاز ۵ — بهبود صفحات عمومی و بلاگ (UI ~10%)
> **اولویت**: پایین | **ریسک**: پایین | **حجم**: ۷ فایل
| # | تسک | تغییرات |
|---|---|---|
| 5.1 | `Index.razor` → حذف inline styles از hero، استفاده از CSS class | UI |
| 5.2 | `About.razor` → حذف inline radial-gradient | UI |
| 5.3 | `Contact.razor` → cleanup minor inline styles | UI |
| 5.4 | `FAQ.razor` → cleanup minor inline styles | UI |
| 5.5 | `Blog/Index` → حذف inline hero styles، استفاده از CSS class | UI |
| 5.6 | `Blog/Post` → حذف inline styles از hero image و typography | UI |
| 5.7 | `RegisterWizard` → بهینه‌سازی wizard-section dark mode | UI |
---
### 🔷 فاز ۶ — Polish نهایی و فرآیندی (UX ~10%)
> **اولویت**: پایین | **ریسک**: بسیار پایین | **حجم**: ۴ فایل + تست
| # | تسک | تغییرات |
|---|---|---|
| 6.1 | Fix routing inconsistency — Categories + Blog/Post | UX |
| 6.2 | MudSnackbar notifications styling | UI |
| 6.3 | Dialog styling consistency (AuthDialog, AddressDialogs) | UI |
| 6.4 | Micro-interactions — button press, card hover, page transition | UI |
| 6.5 | تست کامل Dark Mode در تمام صفحات | UI + QA |
| 6.6 | تست Mobile Responsive در تمام صفحات | UX + QA |
---
## 📈 بخش ۴: جدول تأثیرگذاری
### تأثیر UI (هدف ~۵۰٪ تغییر)
| حوزه | تعداد فایل | درصد تأثیر |
|---|---|---|
| Theme + CSS Variables | ۲ | ۱۵% (تأثیر سراسری) |
| حذف Inline Styles | ۱۱ | ۱۵% |
| یکسان‌سازی Elevation/Rounding | ۲۰+ | ۱۰% |
| بهبود رنگ‌بندی (CSS Variables) | ۸ | ۵% |
| Micro-interactions | سراسری | ۵% |
| **جمع** | | **~۵۰%** |
### تأثیر UX (هدف ~۲۰٪ تغییر)
| حوزه | تعداد فایل | درصد تأثیر |
|---|---|---|
| PageHeader یکپارچه | ۱۲ صفحه | ۸% |
| LoadingState واحد | ۱۵+ صفحه | ۴% |
| EmptyState واحد | ۸+ صفحه | ۳% |
| Container fixes (PackageDetail) | ۲ | ۲% |
| Routing consistency | ۲ | ۱% |
| Flow improvements | ۲ | ۲% |
| **جمع** | | **~۲۰%** |
---
## 🔧 بخش ۵: مشخصات فنی کامپوننت‌های جدید
### ۵.۱ LoadingState Component
```razor
@* Shared/LoadingState.razor *@
<MudStack AlignItems="AlignItems.Center" Class="py-16">
<MudProgressCircular Color="Color.Primary" Indeterminate="true" Size="Size.Large" />
@if (!string.IsNullOrWhiteSpace(Message))
{
<MudText Typo="Typo.body1" Class="mud-text-secondary mt-2">@Message</MudText>
}
</MudStack>
@code {
[Parameter] public string Message { get; set; } = "در حال بارگذاری...";
}
```
### ۵.۲ EmptyState Component
```razor
@* Shared/EmptyState.razor *@
<MudStack AlignItems="AlignItems.Center" Class="py-12" Spacing="3">
<MudIcon Icon="@Icon" Size="Size.Large" Color="Color.Default" Class="mud-text-disabled" />
<MudText Typo="Typo.h6" Class="mud-text-secondary">@Title</MudText>
@if (!string.IsNullOrWhiteSpace(Description))
{
<MudText Typo="Typo.body2" Class="mud-text-secondary" Style="max-width:400px; text-align:center;">
@Description
</MudText>
}
@if (!string.IsNullOrWhiteSpace(ActionText))
{
<MudButton Variant="Variant.Filled" Color="Color.Primary"
Href="@ActionHref" OnClick="@OnAction" Class="mt-2">
@ActionText
</MudButton>
}
</MudStack>
@code {
[Parameter] public string Icon { get; set; } = Icons.Material.Filled.Inbox;
[Parameter] public string Title { get; set; } = "موردی یافت نشد";
[Parameter] public string? Description { get; set; }
[Parameter] public string? ActionText { get; set; }
[Parameter] public string? ActionHref { get; set; }
[Parameter] public EventCallback OnAction { get; set; }
}
```
### ۵.۳ PageHeader ارتقاء‌یافته
```razor
@* Shared/PageHeader.razor — ارتقاء‌یافته *@
<div class="page-header">
<MudStack Spacing="0">
<MudText Typo="Typo.h5">@Title</MudText>
@if (!string.IsNullOrWhiteSpace(Subtitle))
{
<MudText Typo="Typo.body2" Class="mud-text-secondary">@Subtitle</MudText>
}
</MudStack>
@if (!string.IsNullOrWhiteSpace(BackHref))
{
<MudButton Variant="Variant.Text" StartIcon="@Icons.Material.Filled.ArrowBack"
Href="@BackHref">بازگشت</MudButton>
}
else
{
<MudButton Variant="Variant.Text" StartIcon="@Icons.Material.Filled.ArrowBack"
OnClick="GoBack">بازگشت</MudButton>
}
</div>
@code {
[Parameter] public string Title { get; set; } = "";
[Parameter] public string? Subtitle { get; set; }
[Parameter] public string? BackHref { get; set; }
[Inject] private IJSRuntime JS { get; set; } = default!;
private async Task GoBack() => await JS.InvokeVoidAsync("history.back");
}
```
---
## ✅ بخش ۶: چک‌لیست تکمیل هر فاز
### فاز ۱ چک‌لیست:
- [ ] `CustomMudTheme.cs` — Primary→`#6366f1`, PaletteDark اضافه شد
- [ ] `site.css` — CSS Variables جدید (brand, semantic, soft, gradient)
- [ ] `Shared/LoadingState.razor` — ساخته و تست شد
- [ ] `Shared/EmptyState.razor` — ساخته و تست شد
- [ ] `Shared/PageHeader.razor` — Subtitle parameter اضافه شد
- [ ] `.page-container` CSS pattern اضافه شد
- [ ] Build: 0 errors ✅
- [ ] Dark Mode: صحیح ✅
- [ ] Mobile: صحیح ✅
### فاز ۲ چک‌لیست:
- [ ] `Profile/Personal``<PageHeader>`
- [ ] `Profile/Addresses``<PageHeader>` + `<LoadingState>` + `<EmptyState>`
- [ ] `Profile/Wallet``<PageHeader>`
- [ ] `Profile/Settings``<PageHeader>`
- [ ] `Profile/Tree``<PageHeader>`
- [ ] `Profile/WithdrawalRequests``<PageHeader>` + `<EmptyState>`
- [ ] `Profile/ChangePassword``<PageHeader>`
- [ ] `Profile/Index` → inline styles → CSS
- [ ] `Club/*``<PageHeader>` + `<LoadingState>`
- [ ] Build: 0 errors ✅
### فاز ۳ چک‌لیست:
- [ ] `Commission/*``<PageHeader>` + `<LoadingState>`
- [ ] `Network/*``<PageHeader>` + `<LoadingState>`
- [ ] `PackageDetail` → MudContainer wrapper برای loading/error
- [ ] `Checkout` → Elevation fix + inline cleanup
- [ ] `WeeklyBalance` → inline gradient → CSS class
- [ ] Build: 0 errors ✅
### فاز ۴ چک‌لیست:
- [ ] `Store/Products` → hero inline → CSS
- [ ] `Store/ProductDetail` → image inline → CSS class
- [ ] `DiscountStore/ProductDetail` → rgba → variable
- [ ] `Gateway/*` → hardcoded → variable
- [ ] Elevation یکسان‌سازی
- [ ] Container spacing یکسان‌سازی
- [ ] Build: 0 errors ✅
### فاز ۵ چک‌لیست:
- [ ] `Index.razor` → hero inline cleanup
- [ ] `About.razor` → radial-gradient cleanup
- [ ] `Blog/Index` + `Blog/Post` → inline cleanup
- [ ] `Contact.razor` + `FAQ.razor` → minor cleanup
- [ ] Build: 0 errors ✅
### فاز ۶ چک‌لیست:
- [ ] Routing fix (Categories, Blog/Post)
- [ ] MudSnackbar styling
- [ ] Dialog consistency
- [ ] Dark mode full test
- [ ] Mobile responsive full test
- [ ] Build: 0 errors ✅
---
## 📋 بخش ۷: خلاصه تغییرات در یک نگاه
```
فایل‌های تغییر‌یافته:
├── CustomMudTheme.cs [فاز ۱] — ارتقاء کامل تم
├── site.css [فاز ۱-۵] — CSS Variables + class‌های جدید
├── Shared/LoadingState.razor [فاز ۱] — جدید
├── Shared/EmptyState.razor [فاز ۱] — جدید
├── Shared/PageHeader.razor [فاز ۱] — بهبود
├── Profile/* (۷ فایل) [فاز ۲] — PageHeader + LoadingState
├── Profile/Index.razor [فاز ۲] — حذف inline styles
├── Club/* (۲ فایل) [فاز ۲] — PageHeader + LoadingState
├── Commission/* (۲ فایل) [فاز ۳] — PageHeader + LoadingState + cleanup
├── Network/* (۱ فایل) [فاز ۳] — PageHeader + LoadingState
├── PackageDetail.razor [فاز ۳] — Container fix
├── Checkout.razor [فاز ۳] — Elevation + cleanup
├── Store/* (۲ فایل) [فاز ۴] — inline → CSS
├── DiscountStore/ProductDetail [فاز ۴] — rgba → variable
├── Gateway/* (۳ فایل) [فاز ۴] — hardcoded → variable
├── Index.razor [فاز ۵] — hero cleanup
├── About.razor [فاز ۵] — gradient cleanup
├── Blog/* (۲ فایل) [فاز ۵] — inline cleanup
├── Contact.razor + FAQ.razor [فاز ۵] — minor cleanup
└── Categories + Blog/Post [فاز ۶] — routing fix
```
**مجموع فایل‌های تأثیرپذیر**: ~۳۵ فایل
**فایل‌های جدید**: ۲ (LoadingState, EmptyState)
**میزان تغییر UI**: ~۵۰٪
**میزان تغییر UX**: ~۲۰٪
**ریسک شکست**: پایین (تغییرات تدریجی، build verification در هر فاز)
See [totalDoc/INDEX.md](../../totalDoc/INDEX.md) for all documentation.
+2 -2
View File
@@ -11,8 +11,8 @@
<ItemGroup>
<PackageReference Include="DateTimeConverterCL" Version="1.0.0" />
<!-- Replace all FrontOffice.BFF protobuf packages with CMS protobuf -->
<PackageReference Include="Foursat.CMSMicroservice.Protobuf" Version="0.0.180" />
<!--<ProjectReference Include="../../../CMS/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj" />-->
<PackageReference Include="Foursat.CMSMicroservice.Protobuf" Version="0.0.181" />
<!-- <ProjectReference Include="../../../CMS/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj" />-->
<PackageReference Include="MudBlazor" Version="8.14.0" />
<PackageReference Include="Blazored.LocalStorage" Version="4.5.0" />
<PackageReference Include="Mapster" Version="7.4.0" />
@@ -118,7 +118,7 @@
</MudStack>
<MudStack Row="true" Justify="Justify.SpaceBetween">
<MudText Typo="Typo.body2" Class="mud-text-secondary">مالیات بر ارزش افزوده (۹٪):</MudText>
<MudText Typo="Typo.body2" Class="mud-text-secondary">مالیات بر ارزش افزوده (۱۰٪):</MudText>
<MudText Typo="Typo.body2" Class="mud-text-secondary">@FormatPrice(VatAmount)+ تومان</MudText>
</MudStack>
@@ -18,12 +18,12 @@ public partial class Checkout
private string? _notes;
private bool _placing;
private const decimal VAT_RATE = 0.09m;
private const decimal VAT_RATE = 0.10m;
/// <summary>مبلغ درگاه قبل از مالیات (جمع کل - تخفیف)</summary>
private long NetGatewayAmount => DiscountCart.TotalPrice - DiscountCart.TotalDiscount;
/// <summary>مالیات بر ارزش افزوده ۹٪</summary>
/// <summary>مالیات بر ارزش افزوده ۱۰٪</summary>
private long VatAmount => (long)(NetGatewayAmount * VAT_RATE);
/// <summary>مبلغ نهایی قابل پرداخت (شامل VAT)</summary>
@@ -54,6 +54,7 @@ public partial class Index
new(RouteConstants.Profile.Tree, Icons.Material.Filled.AccountTree, "شجره‌نامه", "ساختار تیم", "background:rgba(99,102,241,.12); color:#6366f1;"),
new(RouteConstants.Gateway.StoreChooser, Icons.Material.Filled.Store, "فروشگاه‌ها", "ورود به فروشگاه", "background:rgba(16,185,129,.12); color:#10b981;"),
new(RouteConstants.Profile.Wallet, Icons.Material.Filled.AccountBalanceWallet, "کیف پول", "مدیریت و تاریخچه", "background:rgba(16,185,129,.12); color:#10b981;"),
new(RouteConstants.Profile.MagicWallet, Icons.Material.Filled.AutoAwesome, "کیف‌پول جادویی", "شارژ ×2.5", "background:rgba(168,85,247,.12); color:#a855f7;"),
new(RouteConstants.Profile.WithdrawalRequests, Icons.Material.Filled.RequestPage, "درخواست برداشت", "ثبت و پیگیری", "background:rgba(245,158,11,.12); color:#f59e0b;"),
new(RouteConstants.Club.Membership, Icons.Material.Filled.CardMembership, "باشگاه مشتریان", "عضویت و مزایا", "background:rgba(168,85,247,.12); color:#a855f7;"),
new(RouteConstants.Network.Statistics, Icons.Material.Filled.Groups, "آمار باشگاه", "جزئیات باشگاه", "background:rgba(59,130,246,.12); color:#3b82f6;"),
@@ -0,0 +1,209 @@
@attribute [Route(RouteConstants.Profile.MagicWallet)]
@attribute [Authorize]
<PageTitle>کیف‌پول جادویی</PageTitle>
<MudContainer MaxWidth="MaxWidth.Medium" Class="py-6">
<MudStack Spacing="3">
<PageHeader Title="🪄 کیف‌پول جادویی" BackHref="@RouteConstants.Profile.Wallet" />
@if (_isLoading)
{
<MudPaper Elevation="2" Class="pa-6 rounded-lg text-center">
<MudProgressCircular Color="Color.Primary" Indeterminate="true" />
<MudText Class="mt-3">در حال بارگذاری...</MudText>
</MudPaper>
}
else if (_status.WalletMode != 1)
{
@* ═══ حالت عادی — Magic فعال نیست ═══ *@
<MudPaper Elevation="2" Class="pa-6 rounded-lg text-center">
<MudIcon Icon="@Icons.Material.Filled.Lock" Color="Color.Default" Size="Size.Large" Style="font-size: 64px;" />
<MudText Typo="Typo.h5" Class="mt-4">کیف‌پول جادویی فعال نیست</MudText>
<MudText Typo="Typo.body1" Class="mt-2 mud-text-secondary">
برای فعال شدن کیف‌پول جادویی، ابتدا پکیج پایه را خریداری کنید و سپس
تمام موجودی عادی خود را از فروشگاه خرج کنید. وقتی موجودی عادی به صفر برسد،
کیف‌پول جادویی فعال می‌شود.
</MudText>
<MudButton Variant="Variant.Filled" Color="Color.Primary" Class="mt-4 rounded-pill"
Href="@RouteConstants.Profile.Wallet"
StartIcon="@Icons.Material.Filled.AccountBalanceWallet">
بازگشت به کیف پول
</MudButton>
</MudPaper>
}
else
{
@* ═══ حالت جادویی فعال ═══ *@
@* ─── وضعیت و پیشرفت ─── *@
<MudPaper Elevation="2" Class="pa-5 rounded-xl" Style="background: linear-gradient(135deg, #7c3aed 0%, #a855f7 100%); color: white;">
<MudStack Spacing="2">
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
<MudIcon Icon="@Icons.Material.Filled.AutoAwesome" Size="Size.Large" />
<MudText Typo="Typo.h5">حالت جادویی فعال</MudText>
</MudStack>
<MudGrid Spacing="2">
<MudItem xs="6" sm="3">
<MudText Typo="Typo.caption" Style="opacity:0.8">موجودی فعلی</MudText>
<MudText Typo="Typo.h6">@FormatPrice(_status.Balance)</MudText>
</MudItem>
<MudItem xs="6" sm="3">
<MudText Typo="Typo.caption" Style="opacity:0.8">مجموع واریزی</MudText>
<MudText Typo="Typo.h6">@FormatPrice(_status.MagicTotalDeposited)</MudText>
</MudItem>
<MudItem xs="6" sm="3">
<MudText Typo="Typo.caption" Style="opacity:0.8">مجموع اعتبار</MudText>
<MudText Typo="Typo.h6">@FormatPrice(_status.MagicTotalCredited)</MudText>
</MudItem>
<MudItem xs="6" sm="3">
<MudText Typo="Typo.caption" Style="opacity:0.8">سقف باقیمانده</MudText>
<MudText Typo="Typo.h6">@FormatPrice(_status.MagicRemainingDeposit)</MudText>
</MudItem>
</MudGrid>
@* نوار پیشرفت *@
<MudStack Spacing="0" Class="mt-1">
<MudStack Row="true" Justify="Justify.SpaceBetween">
<MudText Typo="Typo.caption" Style="opacity:0.8">پیشرفت سقف واریز</MudText>
<MudText Typo="Typo.caption" Style="opacity:0.8">@(_progressPercent.ToString("0"))%</MudText>
</MudStack>
<MudProgressLinear Value="@_progressPercent" Color="Color.Warning" Size="Size.Medium"
Rounded="true" Class="mt-1" Style="background: rgba(255,255,255,0.3);" />
</MudStack>
@if (_status.MagicActivatedAt.HasValue)
{
<MudText Typo="Typo.caption" Style="opacity:0.7">
فعال از: @_status.MagicActivatedAt.Value.MiladiToJalaliWithTime()
</MudText>
}
</MudStack>
</MudPaper>
@* ─── فرم شارژ ─── *@
@if (_status.MagicRemainingDeposit > 0)
{
<MudPaper Elevation="2" Class="pa-5 rounded-lg">
<MudText Typo="Typo.h6" Class="mb-3">
<MudIcon Icon="@Icons.Material.Filled.CreditCard" Class="ml-1" />
شارژ کیف‌پول جادویی
</MudText>
<MudAlert Severity="MudBlazor.Severity.Info" Dense="true" Class="mb-3" Icon="@Icons.Material.Filled.Info">
مبلغ واریزی شما <strong>×۲.۵</strong> به موجودی اضافه می‌شود.
مثلاً ۱۰ میلیون واریز = ۲۵ میلیون اعتبار.
</MudAlert>
<MudStack Spacing="2">
<MudNumericField @bind-Value="_chargeAmount"
Label="مبلغ واریز (تومان)"
Variant="Variant.Outlined"
Min="10_000"
Max="@(_status.MagicRemainingDeposit / 10)"
Format="N0"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.Payments"
HelperText="@($"حداکثر: {FormatPrice(_status.MagicRemainingDeposit)} (ریال)")" />
@if (_chargeAmount > 0)
{
<MudPaper Outlined="true" Class="pa-3 rounded-lg" Style="background: #f0fdf4;">
<MudStack Row="true" Justify="Justify.SpaceBetween">
<MudText Typo="Typo.body2">مبلغ واریز:</MudText>
<MudText Typo="Typo.body2">@FormatToman(_chargeAmount)</MudText>
</MudStack>
<MudStack Row="true" Justify="Justify.SpaceBetween">
<MudText Typo="Typo.body2" Color="Color.Success"><strong>اعتبار دریافتی (×۲.۵):</strong></MudText>
<MudText Typo="Typo.body2" Color="Color.Success"><strong>@FormatToman((long)(_chargeAmount * 2.5m))</strong></MudText>
</MudStack>
</MudPaper>
}
@* دکمه‌های مبلغ سریع *@
<MudStack Row="true" Spacing="1" Class="flex-wrap">
@foreach (var preset in _presetAmounts)
{
if (preset * 10 <= _status.MagicRemainingDeposit)
{
<MudButton Variant="Variant.Outlined" Size="Size.Small"
Color="@(_chargeAmount == preset ? Color.Primary : Color.Default)"
OnClick="() => _chargeAmount = preset"
Class="rounded-pill">
@FormatToman(preset)
</MudButton>
}
}
</MudStack>
<MudButton Variant="Variant.Filled"
Color="Color.Primary"
Size="Size.Large"
FullWidth="true"
Disabled="@(_chargeAmount <= 0 || _isProcessing)"
OnClick="StartMagicCharge"
StartIcon="@Icons.Material.Filled.Payment"
Class="rounded-lg mt-2">
@if (_isProcessing)
{
<MudProgressCircular Indeterminate="true" Size="Size.Small" Class="ml-2" />
<span>در حال انتقال به درگاه...</span>
}
else
{
<span>پرداخت از درگاه</span>
}
</MudButton>
</MudStack>
</MudPaper>
}
else
{
<MudAlert Severity="MudBlazor.Severity.Warning" Class="rounded-lg">
سقف شارژ جادویی در این دور پر شده است.
با خرج کردن موجودی و خرید مجدد پکیج، سقف ریست می‌شود.
</MudAlert>
}
@* ─── قوانین ─── *@
<MudExpansionPanels Elevation="1" Class="rounded-lg">
<MudExpansionPanel Text="قوانین کیف‌پول جادویی" MaxHeight="500" IsInitiallyExpanded="false">
<MudList T="string" Dense="true">
<MudListItem T="string" Icon="@Icons.Material.Filled.CheckCircle" IconColor="Color.Success">
هر مبلغ واریزی ×۲.۵ به موجودی اضافه می‌شود
</MudListItem>
<MudListItem T="string" Icon="@Icons.Material.Filled.CheckCircle" IconColor="Color.Success">
سقف واریز در هر دور: ۱۰۰ میلیون تومان
</MudListItem>
<MudListItem T="string" Icon="@Icons.Material.Filled.CheckCircle" IconColor="Color.Success">
حداکثر اعتبار: ۲۵۰ میلیون تومان (سود ۱۵۰٪)
</MudListItem>
<MudListItem T="string" Icon="@Icons.Material.Filled.Warning" IconColor="Color.Warning">
در حالت جادویی، کمیسیون و پورسانت غیرفعال است
</MudListItem>
<MudListItem T="string" Icon="@Icons.Material.Filled.Info" IconColor="Color.Info">
بعد از اتمام سقف و خرج موجودی، با خرید مجدد پکیج دور جدید شروع می‌شود
</MudListItem>
</MudList>
</MudExpansionPanel>
</MudExpansionPanels>
}
@* ─── نتیجه پرداخت (اگه از callback برگشته) ─── *@
@if (_paymentResult != null)
{
<MudAlert Severity="@(_paymentResult == "success" ? MudBlazor.Severity.Success : MudBlazor.Severity.Error)"
Class="rounded-lg" Variant="Variant.Filled">
@if (_paymentResult == "success")
{
<span>شارژ جادویی با موفقیت انجام شد! ✅</span>
}
else
{
<span>پرداخت ناموفق بود. لطفاً دوباره تلاش کنید.</span>
}
</MudAlert>
}
</MudStack>
</MudContainer>
@@ -0,0 +1,79 @@
using DateTimeConverterCL;
using FrontOffice.Main.Utilities;
using Microsoft.AspNetCore.Components;
using MudBlazor;
namespace FrontOffice.Main.Pages.Profile;
public partial class MagicWallet : ComponentBase
{
private MagicWalletStatus _status = new(0, 0, 0, 0, 0, 0, null);
private bool _isLoading = true;
private bool _isProcessing;
private long _chargeAmount;
private double _progressPercent;
private string? _paymentResult;
private readonly long[] _presetAmounts = { 1_000_000, 5_000_000, 10_000_000, 20_000_000, 50_000_000, 100_000_000 };
[SupplyParameterFromQuery(Name = "payment")]
public string? PaymentQueryParam { get; set; }
protected override async Task OnInitializedAsync()
{
_paymentResult = PaymentQueryParam;
await LoadStatus();
}
private async Task LoadStatus()
{
_isLoading = true;
_status = await WalletService.GetMagicWalletStatusAsync();
_progressPercent = _status.MagicMaxDeposit > 0
? (double)_status.MagicTotalDeposited / _status.MagicMaxDeposit * 100
: 0;
_isLoading = false;
}
private async Task StartMagicCharge()
{
if (_chargeAmount <= 0 || _isProcessing) return;
_isProcessing = true;
StateHasChanged();
try
{
// تبدیل تومان به ریال
var amountInRial = _chargeAmount * 10;
var (success, gatewayUrl, error) = await WalletService.InitiateMagicChargeAsync(amountInRial);
if (success && !string.IsNullOrEmpty(gatewayUrl))
{
Navigation.NavigateTo(gatewayUrl, forceLoad: true);
}
else
{
Snackbar.Add(error ?? "خطا در ایجاد درخواست پرداخت", Severity.Error);
}
}
catch (Exception ex)
{
Snackbar.Add($"خطا: {ex.Message}", Severity.Error);
}
finally
{
_isProcessing = false;
StateHasChanged();
}
}
private static string FormatPrice(long priceInRial)
=> string.Format("{0:N0} تومان", priceInRial / 10);
private static string FormatToman(long toman)
=> string.Format("{0:N0} تومان", toman);
}
@@ -22,6 +22,7 @@ public static class RouteConstants
public const string ChangePassword = "/profile/change-password";
public const string Tree = "/profile/tree";
public const string Wallet = "/profile/wallet";
public const string MagicWallet = "/profile/magic-wallet";
public const string WithdrawalRequests = "/profile/withdrawal-requests";
}
+3 -3
View File
@@ -17,9 +17,9 @@ public class VATService
private const string VAT_PERCENTAGE_KEY = "vat_percentage";
private const string VAT_DATE_KEY = "vat_date";
// مقادیر پیش‌فرض
private double _vatRate = 0.0999;
private int _vatPercentage = 99;
// مقادیر پیش‌فرض (10%)
private double _vatRate = 0.10;
private int _vatPercentage = 10;
private bool _isEnabled = true;
private bool _isLoaded = false;
@@ -25,6 +25,16 @@ public enum WithdrawalMethodClient
}
public record WithdrawalSettings(long MinWithdrawalAmount);
public record MagicWalletStatus(
int WalletMode,
long MagicTotalDeposited,
long MagicTotalCredited,
long MagicMaxDeposit,
long MagicRemainingDeposit,
long Balance,
DateTime? MagicActivatedAt
);
public class WalletService
{
private readonly CMSMicroservice.Protobuf.Protos.UserWallet.UserWalletContract.UserWalletContractClient _client;
@@ -183,4 +193,64 @@ public class WalletService
return new WithdrawalSettings(0);
}
}
// ============= Magic Wallet =============
public async Task<MagicWalletStatus> GetMagicWalletStatusAsync()
{
try
{
var response = await _client.GetMagicWalletStatusAsync(new Empty());
DateTime? activatedAt = response.MagicActivatedAt != null
? response.MagicActivatedAt.ToDateTime().ToLocalTime()
: null;
return new MagicWalletStatus(
response.WalletMode,
response.MagicTotalDeposited,
response.MagicTotalCredited,
response.MagicMaxDeposit,
response.MagicRemainingDeposit,
response.Balance,
activatedAt
);
}
catch
{
return new MagicWalletStatus(0, 0, 0, 0, 0, 0, null);
}
}
public async Task<(bool Success, string? GatewayUrl, string? Error)> InitiateMagicChargeAsync(long amount)
{
try
{
var response = await _client.InitiateMagicChargeAsync(new InitiateMagicChargeRequest
{
Amount = amount
});
if (response.IsSuccess)
return (true, response.GatewayUrl, null);
return (false, null, response.ErrorMessage);
}
catch (Grpc.Core.RpcException ex)
{
return (false, null, ex.Status.Detail ?? "خطا در ارتباط با سرور");
}
}
public int GetWalletMode()
{
try
{
var response = _client.GetCustomerWallet(new Empty());
return response.WalletMode;
}
catch
{
return 0;
}
}
}