Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Successful in 1m12s
Build and Deploy to Production / build-and-deploy (push) Successful in 1m12s
Features merged: - Discount store UI improvements (100% auto-discount, VAT breakdown) - PaymentStatus display on orders (Pending/Success/Failed/Refunded) - Failed payment UX with re-order link - Product discount badges show actual percent - Various UI and bug fixes Production workflow preserved.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
name: Build and Deploy
|
||||
name: Build and Deploy to Kubernetes
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -6,63 +6,69 @@ on:
|
||||
- kub-stage
|
||||
|
||||
env:
|
||||
REGISTRY: gitea-svc:3000
|
||||
REGISTRY: 194.5.195.53:30080
|
||||
IMAGE_NAME: admin/frontoffice
|
||||
K8S_SERVER: 194.5.195.53
|
||||
|
||||
jobs:
|
||||
build:
|
||||
build-and-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: docker:latest
|
||||
image: 194.5.195.53:32082/docker-sshpass:latest
|
||||
options: --privileged
|
||||
steps:
|
||||
- name: Start Docker daemon with insecure registry
|
||||
- name: Start Docker daemon
|
||||
run: |
|
||||
mkdir -p /etc/docker
|
||||
cat > /etc/docker/daemon.json << 'DAEMON'
|
||||
{
|
||||
"insecure-registries": ["194.5.195.53:32500", "194.5.195.53:32082", "git.kbs1.ir", "gitea-svc:3000"],
|
||||
"dns": ["0.0.0.0"]
|
||||
"insecure-registries": ["194.5.195.53:30080", "194.5.195.53:32500", "194.5.195.53:32082"]
|
||||
}
|
||||
DAEMON
|
||||
echo "🚀 Starting Docker daemon..."
|
||||
dockerd --iptables=false --ip6tables=false --bridge=none --storage-driver=vfs &
|
||||
|
||||
# بدون اینترنت - فقط از cached images
|
||||
dockerd &
|
||||
for i in $(seq 1 30); do
|
||||
docker info >/dev/null 2>&1 && break || sleep 2
|
||||
# Wait up to 3 minutes for Docker to be ready
|
||||
for i in $(seq 1 90); do
|
||||
if docker info >/dev/null 2>&1; then
|
||||
echo "✅ Docker daemon is ready (attempt $i)"
|
||||
docker version
|
||||
break
|
||||
else
|
||||
echo "⏳ Waiting for Docker daemon... (attempt $i/90)"
|
||||
sleep 2
|
||||
fi
|
||||
done
|
||||
docker info
|
||||
|
||||
# بررسی وجود cached images
|
||||
echo "📊 Checking cached base images..."
|
||||
docker images | grep -E "mcr.microsoft.com|nginx" || echo "⚠️ WARNING: Base images not cached!"
|
||||
# Final check
|
||||
if ! docker info >/dev/null 2>&1; then
|
||||
echo "❌ Docker daemon failed to start after 3 minutes"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Checkout code
|
||||
run: |
|
||||
git clone --depth 1 --branch kub-stage http://gitea-svc:3000/admin/FrontOffice.git .
|
||||
git log -1 --format="%H %s"
|
||||
|
||||
- name: Login to Docker registries
|
||||
run: |
|
||||
echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login 194.5.195.53:32082 -u admin --password-stdin
|
||||
echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login ${{ env.REGISTRY }} -u admin --password-stdin
|
||||
|
||||
- name: Build Docker Image
|
||||
run: |
|
||||
cd src
|
||||
# استفاده از cached base images (بدون دانلود)
|
||||
DOCKER_BUILDKIT=0 docker build -f FrontOffice.Main/Dockerfile \
|
||||
-t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} \
|
||||
-t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest \
|
||||
.
|
||||
|
||||
DOCKER_BUILDKIT=0 docker build --network host -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest -f FrontOffice.Main/Dockerfile .
|
||||
|
||||
- name: Push to Registry
|
||||
run: |
|
||||
echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login ${{ env.REGISTRY }} -u admin --password-stdin
|
||||
docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
|
||||
docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||
|
||||
- name: Build and Push Complete
|
||||
- name: Deploy to Kubernetes
|
||||
run: |
|
||||
echo "🎉 Build and push completed successfully!"
|
||||
echo "📦 Image pushed to: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}"
|
||||
echo "📦 Latest tag: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest"
|
||||
echo ""
|
||||
echo "🚀 To deploy manually on server:"
|
||||
echo "kubectl set image deployment/frontoffice frontoffice=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}"
|
||||
export SSHPASS="${{ secrets.SERVER_PASSWORD }}"
|
||||
sshpass -e ssh -o StrictHostKeyChecking=no root@${{ env.K8S_SERVER }} "
|
||||
kubectl rollout restart deployment/frontoffice
|
||||
kubectl rollout status deployment/frontoffice --timeout=180s
|
||||
"
|
||||
echo "✅ Deployed!"
|
||||
|
||||
@@ -0,0 +1,273 @@
|
||||
# 📋 تاریخچه تغییرات FrontOffice — کارا بازار سلامت
|
||||
|
||||
> **آخرین بروزرسانی**: بهمن ۱۴۰۴ (فوریه ۲۰۲۶)
|
||||
> **فریمورک**: Blazor Server + MudBlazor 8.14 + .NET 9
|
||||
|
||||
---
|
||||
|
||||
## 🔖 نسخه ۲.۶.۰ — بهمن ۱۴۰۴ (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) |
|
||||
@@ -0,0 +1,542 @@
|
||||
# 🎨 پلن جامع یکپارچهسازی UI/UX — کارا بازار سلامت
|
||||
|
||||
> **تاریخ**: بهمن ۱۴۰۴
|
||||
> **وضعیت**: ✅ **تمام ۶ فاز + فاز ۷ (ناوبری و امنیت) تکمیل شد**
|
||||
> **هدف**: یکپارچهسازی طراحی 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 در هر فاز)
|
||||
@@ -5,22 +5,29 @@ using Grpc.Net.Client;
|
||||
using MudBlazor.Services;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using FrontOffice.BFF.Category.Protobuf.Protos.Category;
|
||||
using FrontOffice.BFF.Package.Protobuf.Protos.Package;
|
||||
using FrontOffice.BFF.Transaction.Protobuf.Protos.Transaction;
|
||||
using FrontOffice.BFF.User.Protobuf.Protos.User;
|
||||
using FrontOffice.BFF.UserAddress.Protobuf.Protos.UserAddress;
|
||||
using FrontOffice.BFF.UserOrder.Protobuf.Protos.UserOrder;
|
||||
using FrontOffice.BFF.UserWallet.Protobuf.Protos.UserWallet;
|
||||
using FrontOffice.BFF.ShopingCart.Protobuf.Protos.ShopingCart;
|
||||
// New Proto imports
|
||||
using FrontOffice.BFF.ClubMembership.Protobuf.Protos.ClubMembership;
|
||||
using FrontOffice.BFF.Commission.Protobuf.Protos.Commission;
|
||||
using FrontOffice.BFF.NetworkMembership.Protobuf.Protos.NetworkMembership;
|
||||
using FrontOffice.BFF.DiscountShop.Protobuf.Protos.DiscountShop;
|
||||
using FrontOffice.BFF.City.Protobuf;
|
||||
using FrontOffice.BFF.Configuration.Protobuf.Protos.Configuration;
|
||||
using FrontOffice.BFF.Configuration.Protobuf.Protos.AppVersion;
|
||||
// Updated imports to use CMS protobuf instead of FrontOffice.BFF
|
||||
using CMSMicroservice.Protobuf.Protos.Category;
|
||||
using CMSMicroservice.Protobuf.Protos.City;
|
||||
using CMSMicroservice.Protobuf.Protos.Package;
|
||||
using CMSMicroservice.Protobuf.Protos.Products;
|
||||
using CMSMicroservice.Protobuf.Protos.Transactions;
|
||||
using CMSMicroservice.Protobuf.Protos.User;
|
||||
using CMSMicroservice.Protobuf.Protos.UserCarts;
|
||||
using CMSMicroservice.Protobuf.Protos.UserOrder;
|
||||
using CMSMicroservice.Protobuf.Protos.UserWallet;
|
||||
using CMSMicroservice.Protobuf.Protos.UserWalletChangeLog;
|
||||
using CMSMicroservice.Protobuf.Protos.UserAddress;
|
||||
using CMSMicroservice.Protobuf.Protos.Configuration;
|
||||
using CMSMicroservice.Protobuf.Protos.NetworkMembership;
|
||||
using CMSMicroservice.Protobuf.Protos.Commission;
|
||||
using CMSMicroservice.Protobuf.Protos.AppVersion;
|
||||
using CMSMicroservice.Protobuf.Protos.SitePage;
|
||||
using CMSMicroservice.Protobuf.Protos.BlogPost;
|
||||
using CMSMicroservice.Protobuf.Protos.BlogCategory;
|
||||
using CMSMicroservice.Protobuf.Protos.DiscountProduct;
|
||||
using CMSMicroservice.Protobuf.Protos.DiscountCategory;
|
||||
using CMSMicroservice.Protobuf.Protos.DiscountShoppingCart;
|
||||
using CMSMicroservice.Protobuf.Protos.DiscountOrder;
|
||||
using FrontOffice.Main.Utilities;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyInjection;
|
||||
@@ -64,6 +71,18 @@ public static class ConfigureServices
|
||||
services.AddScoped<CommissionService>();
|
||||
// App Version Service for cache invalidation
|
||||
services.AddScoped<AppVersionService>();
|
||||
// Main Service for core functionality
|
||||
services.AddScoped<MainService>();
|
||||
// Site Page service for dynamic content (About, Contact)
|
||||
services.AddScoped<SitePageService>();
|
||||
// Blog Post service for landing page latest posts & blog pages
|
||||
services.AddScoped<BlogPostService>();
|
||||
// Blog Category service for blog sidebar filters
|
||||
services.AddScoped<BlogCategoryService>();
|
||||
// Discount Store services
|
||||
services.AddScoped<DiscountProductService>();
|
||||
services.AddScoped<DiscountCartService>();
|
||||
services.AddScoped<DiscountOrderService>();
|
||||
// Device detection: very light, dependency-free
|
||||
services.AddTransient<IDeviceDetector, DeviceDetector>();
|
||||
// PDF generation (Chromium only)
|
||||
@@ -95,25 +114,35 @@ public static class ConfigureServices
|
||||
};
|
||||
});
|
||||
|
||||
// Register gRPC clients with authentication
|
||||
services.AddScoped(CreateAuthenticatedClient<PackageContract.PackageContractClient>);
|
||||
services.AddScoped(CreateAuthenticatedClient<UserContract.UserContractClient>);
|
||||
services.AddScoped(CreateAuthenticatedClient<UserAddressContract.UserAddressContractClient>);
|
||||
services.AddScoped(CreateAuthenticatedClient<UserOrderContract.UserOrderContractClient>);
|
||||
services.AddScoped(CreateAuthenticatedClient<UserWalletContract.UserWalletContractClient>);
|
||||
services.AddScoped(CreateAuthenticatedClient<CategoryContract.CategoryContractClient>);
|
||||
// Products gRPC
|
||||
services.AddScoped(CreateAuthenticatedClient<FrontOffice.BFF.Products.Protobuf.Protos.Products.ProductsContract.ProductsContractClient>);
|
||||
services.AddScoped(CreateAuthenticatedClient<TransactionContract.TransactionContractClient>);
|
||||
services.AddScoped(CreateAuthenticatedClient<ShopingCartContract.ShopingCartContractClient>);
|
||||
// New gRPC clients for Club, Network, Commission, DiscountShop
|
||||
services.AddScoped(CreateAuthenticatedClient<ClubMembershipContract.ClubMembershipContractClient>);
|
||||
services.AddScoped(CreateAuthenticatedClient<CommissionContract.CommissionContractClient>);
|
||||
services.AddScoped(CreateAuthenticatedClient<ConfigurationContract.ConfigurationContractClient>);
|
||||
services.AddScoped(CreateAuthenticatedClient<NetworkMembershipContract.NetworkMembershipContractClient>);
|
||||
services.AddScoped(CreateAuthenticatedClient<DiscountShopContract.DiscountShopContractClient>);
|
||||
services.AddScoped(CreateAuthenticatedClient<CityContract.CityContractClient>);
|
||||
services.AddScoped(CreateAuthenticatedClient<AppVersionContract.AppVersionContractClient>);
|
||||
// Register gRPC clients with authentication - Updated for CMS
|
||||
services.AddScoped(CreateAuthenticatedClient<CMSMicroservice.Protobuf.Protos.Package.PackageContract.PackageContractClient>);
|
||||
services.AddScoped(CreateAuthenticatedClient<CMSMicroservice.Protobuf.Protos.User.UserContract.UserContractClient>);
|
||||
services.AddScoped(CreateAuthenticatedClient<CMSMicroservice.Protobuf.Protos.UserOrder.UserOrderContract.UserOrderContractClient>);
|
||||
services.AddScoped(CreateAuthenticatedClient<CMSMicroservice.Protobuf.Protos.UserWallet.UserWalletContract.UserWalletContractClient>);
|
||||
services.AddScoped(CreateAuthenticatedClient<CMSMicroservice.Protobuf.Protos.Category.CategoryContract.CategoryContractClient>);
|
||||
services.AddScoped(CreateAuthenticatedClient<CMSMicroservice.Protobuf.Protos.Products.ProductsContract.ProductsContractClient>);
|
||||
services.AddScoped(CreateAuthenticatedClient<CMSMicroservice.Protobuf.Protos.Transactions.TransactionsContract.TransactionsContractClient>);
|
||||
services.AddScoped(CreateAuthenticatedClient<CMSMicroservice.Protobuf.Protos.UserCarts.UserCartsContract.UserCartsContractClient>);
|
||||
services.AddScoped(CreateAuthenticatedClient<CMSMicroservice.Protobuf.Protos.City.CityContract.CityContractClient>);
|
||||
services.AddScoped(CreateAuthenticatedClient<CMSMicroservice.Protobuf.Protos.UserAddress.UserAddressContract.UserAddressContractClient>);
|
||||
services.AddScoped(CreateAuthenticatedClient<CMSMicroservice.Protobuf.Protos.UserWalletChangeLog.UserWalletChangeLogContract.UserWalletChangeLogContractClient>);
|
||||
services.AddScoped(CreateAuthenticatedClient<CMSMicroservice.Protobuf.Protos.ClubMembership.ClubMembershipContract.ClubMembershipContractClient>);
|
||||
services.AddScoped(CreateAuthenticatedClient<CMSMicroservice.Protobuf.Protos.OtpToken.OtpTokenContract.OtpTokenContractClient>);
|
||||
services.AddScoped(CreateAuthenticatedClient<CMSMicroservice.Protobuf.Protos.Configuration.ConfigurationContract.ConfigurationContractClient>);
|
||||
services.AddScoped(CreateAuthenticatedClient<CMSMicroservice.Protobuf.Protos.NetworkMembership.NetworkMembershipContract.NetworkMembershipContractClient>);
|
||||
services.AddScoped(CreateAuthenticatedClient<CMSMicroservice.Protobuf.Protos.Commission.CommissionContract.CommissionContractClient>);
|
||||
services.AddScoped(CreateAuthenticatedClient<CMSMicroservice.Protobuf.Protos.AppVersion.AppVersionContract.AppVersionContractClient>);
|
||||
services.AddScoped(CreateAuthenticatedClient<CMSMicroservice.Protobuf.Protos.SitePage.SitePageContract.SitePageContractClient>);
|
||||
services.AddScoped(CreateAuthenticatedClient<CMSMicroservice.Protobuf.Protos.BlogPost.BlogPostContract.BlogPostContractClient>);
|
||||
services.AddScoped(CreateAuthenticatedClient<CMSMicroservice.Protobuf.Protos.BlogCategory.BlogCategoryContract.BlogCategoryContractClient>);
|
||||
// Image Resolver Service
|
||||
services.AddScoped(CreateAuthenticatedClient<CMSMicroservice.Protobuf.Protos.ImageResolver.ImageResolverContract.ImageResolverContractClient>);
|
||||
services.AddScoped<ImageCacheService>();
|
||||
// Discount Store gRPC clients
|
||||
services.AddScoped(CreateAuthenticatedClient<DiscountProductContract.DiscountProductContractClient>);
|
||||
services.AddScoped(CreateAuthenticatedClient<DiscountCategoryContract.DiscountCategoryContractClient>);
|
||||
services.AddScoped(CreateAuthenticatedClient<DiscountShoppingCartContract.DiscountShoppingCartContractClient>);
|
||||
services.AddScoped(CreateAuthenticatedClient<DiscountOrderContract.DiscountOrderContractClient>);
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
@@ -10,29 +10,9 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DateTimeConverterCL" Version="1.0.0" />
|
||||
<PackageReference Include="Foursat.FrontOffice.BFF.City.Protobuf" Version="0.0.2" />
|
||||
<PackageReference Include="Foursat.FrontOffice.BFF.ClubMembership.Protobuf" Version="0.0.4" />
|
||||
<PackageReference Include="Foursat.FrontOffice.BFF.Commission.Protobuf" Version="0.0.6" />
|
||||
<PackageReference Include="Foursat.FrontOffice.BFF.Configuration.Protobuf" Version="0.0.4" />
|
||||
<!-- <PackageReference Include="Foursat.FrontOffice.BFF.ClubMembership.Protobuf" Version="0.0.3" /> -->
|
||||
<!-- <PackageReference Include="Foursat.FrontOffice.BFF.Commission.Protobuf" Version="0.0.2" /> -->
|
||||
<PackageReference Include="Foursat.FrontOffice.BFF.DiscountShop.Protobuf" Version="0.0.3" />
|
||||
<PackageReference Include="Foursat.FrontOffice.BFF.NetworkMembership.Protobuf" Version="0.0.5" />
|
||||
<PackageReference Include="Foursat.FrontOffice.BFF.Package.Protobuf" Version="0.0.114" />
|
||||
<!-- <PackageReference Include="Foursat.FrontOffice.BFF.NetworkMembership.Protobuf" Version="0.0.2" />-->
|
||||
<!-- <PackageReference Include="Foursat.FrontOffice.BFF.Package.Protobuf" Version="0.0.113" /> -->
|
||||
<PackageReference Include="Foursat.FrontOffice.BFF.Products.Protobuf" Version="0.0.18" />
|
||||
<PackageReference Include="Foursat.FrontOffice.BFF.Transaction.Protobuf" Version="0.0.113" />
|
||||
<PackageReference Include="Foursat.FrontOffice.BFF.Category.Protobuf" Version="0.0.14" />
|
||||
<PackageReference Include="Foursat.FrontOffice.BFF.User.Protobuf" Version="0.0.118" />
|
||||
<!-- <PackageReference Include="Foursat.FrontOffice.BFF.User.Protobuf" Version="0.0.117" /> -->
|
||||
<PackageReference Include="Foursat.FrontOffice.BFF.UserAddress.Protobuf" Version="0.0.116" />
|
||||
<!-- <PackageReference Include="Foursat.FrontOffice.BFF.UserOrder.Protobuf" Version="0.0.115" />-->
|
||||
<PackageReference Include="Foursat.FrontOffice.BFF.ShopingCart.Protobuf" Version="0.0.17" />
|
||||
<PackageReference Include="Foursat.FrontOffice.BFF.UserOrder.Protobuf" Version="0.0.116" />
|
||||
<PackageReference Include="Foursat.FrontOffice.BFF.UserWallet.Protobuf" Version="0.0.16" />
|
||||
<!-- UserWallet moved to ProjectReference for latest proto with WeekDefinitionId -->
|
||||
<!-- <PackageReference Include="Foursat.FrontOffice.BFF.UserWallet.Protobuf" Version="0.0.15" /> -->
|
||||
<!-- Replace all FrontOffice.BFF protobuf packages with CMS protobuf -->
|
||||
<PackageReference Include="Foursat.CMSMicroservice.Protobuf" Version="0.0.179" />
|
||||
<!--<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" />
|
||||
|
||||
@@ -2,24 +2,13 @@
|
||||
<configuration>
|
||||
<packageSources>
|
||||
<clear />
|
||||
<!-- Nexus as primary source (proxies nuget.org + caches packages) -->
|
||||
<!-- Nexus (hosts FourSat packages + proxies nuget.org) -->
|
||||
<add key="Nexus" value="http://194.5.195.53:32081/repository/nuget-all/index.json" allowInsecureConnections="true" />
|
||||
<!-- Backup: Direct Gitea registries -->
|
||||
<add key="FourSat" value="https://git.afrino.co/api/packages/FourSat/nuget/index.json" />
|
||||
<add key="Afrino" value="https://git.afrino.co/api/packages/Afrino/nuget/index.json" />
|
||||
</packageSources>
|
||||
<packageSourceCredentials>
|
||||
<Nexus>
|
||||
<add key="Username" value="admin" />
|
||||
<add key="ClearTextPassword" value="87zH26nbqT" />
|
||||
</Nexus>
|
||||
<FourSat>
|
||||
<add key="Username" value="masoud" />
|
||||
<add key="ClearTextPassword" value="87zH26nbqT" />
|
||||
</FourSat>
|
||||
<Afrino>
|
||||
<add key="Username" value="systemuser" />
|
||||
<add key="ClearTextPassword" value="sZSA7PTiv3pUSQZ" />
|
||||
</Afrino>
|
||||
</packageSourceCredentials>
|
||||
</configuration>
|
||||
|
||||
@@ -1,192 +1,280 @@
|
||||
@attribute [Route(RouteConstants.About.Index)]
|
||||
@inject NavigationManager Navigation
|
||||
@inject SitePageService SitePageService
|
||||
|
||||
<PageTitle>درباره ما | کارا بازار سلامت</PageTitle>
|
||||
|
||||
<!-- Hero Section -->
|
||||
<section class="about-hero-section py-16">
|
||||
<MudContainer MaxWidth="MaxWidth.Large">
|
||||
<MudGrid Justify="Justify.Center" Spacing="4">
|
||||
<MudItem xs="12" md="6">
|
||||
<MudChip T="string" Color="Color.Secondary" Variant="Variant.Filled" Class="mb-2">درباره کارا بازار سلامت</MudChip>
|
||||
<MudStack Spacing="3">
|
||||
<MudText Typo="Typo.h2" Class="mb-3">
|
||||
پلتفرم هوشمند تیمسازی و مدیریت فروش
|
||||
</MudText>
|
||||
<MudText Typo="Typo.body1" Class="mud-text-secondary mb-4">
|
||||
ما با ارائه ابزارهای نوآورانه، به کسبوکارها کمک میکنیم تا تیمهای فروش خود را گسترش دهند و به صورت کارآمد مدیریت کنند.
|
||||
</MudText>
|
||||
<MudStack Row="true" Spacing="2">
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
Size="Size.Large"
|
||||
OnClick="() => Navigation.NavigateTo(RouteConstants.Main.MainPage)">
|
||||
شروع کنید
|
||||
</MudButton>
|
||||
<MudButton Variant="Variant.Outlined"
|
||||
Color="Color.Primary"
|
||||
Size="Size.Large"
|
||||
OnClick="ScrollToContact">
|
||||
تماس با ما
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
</MudStack>
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="6">
|
||||
<MudPaper Class="pa-8 rounded-xl" Style="background: radial-gradient(600px 280px at 120% 0, #daccff 0, transparent 60%), radial-gradient(600px 280px at -10% 100%, #ffe2f2 0, transparent 60%), linear-gradient(180deg, #fff, #fbfaff);">
|
||||
<MudImage Src="images/team-work.jpg"
|
||||
Alt="جلسه تیم فروش"
|
||||
ObjectFit="ObjectFit.Cover"
|
||||
ObjectPosition="ObjectPosition.Center"
|
||||
Style="width:100%"
|
||||
Class="rounded-xl" />
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
@if (_loading)
|
||||
{
|
||||
<MudContainer MaxWidth="MaxWidth.Large" Class="py-16">
|
||||
<LoadingState />
|
||||
</MudContainer>
|
||||
</section>
|
||||
|
||||
<MudStack Spacing="8">
|
||||
|
||||
<!-- Mission & Vision -->
|
||||
<section class="py-12">
|
||||
}
|
||||
else
|
||||
{
|
||||
<!-- Hero Section -->
|
||||
<section class="about-hero-section py-16">
|
||||
<MudContainer MaxWidth="MaxWidth.Large">
|
||||
<MudText Typo="Typo.h3" Align="Align.Center" Class="mb-8">چشمانداز و مأموریت ما</MudText>
|
||||
<MudGrid Spacing="4" Justify="Justify.Center">
|
||||
<MudGrid Justify="Justify.Center" Spacing="4">
|
||||
<MudItem xs="12" md="6">
|
||||
<MudPaper Elevation="3" Class="pa-6 text-center h-100">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Visibility" Size="Size.Large" Color="Color.Primary" Class="mb-4" />
|
||||
<MudText Typo="Typo.h5" Class="mb-3">چشمانداز</MudText>
|
||||
<MudText Typo="Typo.body1" Class="mud-text-secondary">
|
||||
تبدیل شدن به پیشروترین پلتفرم مدیریت تیمهای فروش در منطقه، با ارائه راهکارهای هوشمند و کاربرپسند برای کسبوکارهای کوچک و بزرگ.
|
||||
<MudChip T="string" Color="Color.Secondary" Variant="Variant.Filled" Class="mb-2">درباره کارا بازار سلامت</MudChip>
|
||||
<MudStack Spacing="3">
|
||||
<MudText Typo="Typo.h2" Class="mb-3">
|
||||
@(_page?.HeroTitle ?? "پلتفرم هوشمند تیمسازی و مدیریت فروش")
|
||||
</MudText>
|
||||
</MudPaper>
|
||||
<MudText Typo="Typo.body1" Class="mud-text-secondary mb-4">
|
||||
@(_page?.HeroSubtitle ?? "ما با ارائه ابزارهای نوآورانه، به کسبوکارها کمک میکنیم تا تیمهای فروش خود را گسترش دهند و به صورت کارآمد مدیریت کنند.")
|
||||
</MudText>
|
||||
<MudStack Row="true" Spacing="2" Class="flex-wrap">
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
Size="Size.Large"
|
||||
OnClick="() => Navigation.NavigateTo(RouteConstants.Main.MainPage)">
|
||||
شروع کنید
|
||||
</MudButton>
|
||||
<MudButton Variant="Variant.Outlined"
|
||||
Color="Color.Primary"
|
||||
Size="Size.Large"
|
||||
OnClick="() => Navigation.NavigateTo(RouteConstants.Contact.Index)">
|
||||
تماس با ما
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
</MudStack>
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="6">
|
||||
<MudPaper Elevation="3" Class="pa-6 text-center h-100">
|
||||
<MudIcon Icon="@Icons.Material.Filled.TrackChanges" Size="Size.Large" Color="Color.Success" Class="mb-4" />
|
||||
<MudText Typo="Typo.h5" Class="mb-3">مأموریت</MudText>
|
||||
<MudText Typo="Typo.body1" Class="mud-text-secondary">
|
||||
توانمندسازی کسبوکارها از طریق فناوریهای نوین، سادهسازی فرآیندهای پیچیده و ایجاد کارا بازار سلامتهای جدید برای رشد و توسعه پایدار.
|
||||
</MudText>
|
||||
<MudPaper Class="pa-8 rounded-xl" Style="background: radial-gradient(600px 280px at 120% 0, #daccff 0, transparent 60%), radial-gradient(600px 280px at -10% 100%, #ffe2f2 0, transparent 60%), linear-gradient(180deg, #fff, #fbfaff);">
|
||||
<AppImage Path="@(_page?.HeroImagePath ?? "images/team-work.jpg")"
|
||||
Alt="جلسه تیم فروش"
|
||||
ObjectFit="ObjectFit.Cover"
|
||||
ObjectPosition="ObjectPosition.Center"
|
||||
Style="width:100%"
|
||||
Class="rounded-xl" />
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudContainer>
|
||||
</section>
|
||||
|
||||
<!-- Company Values -->
|
||||
<section class="py-12 bg-grey-50">
|
||||
<MudContainer MaxWidth="MaxWidth.Large">
|
||||
<MudText Typo="Typo.h3" Align="Align.Center" Class="mb-8">ارزشهای ما</MudText>
|
||||
<MudGrid Spacing="3" Justify="Justify.Center">
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudPaper Elevation="2" Class="pa-4 rounded-xl text-center">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Security" Size="Size.Large" Color="Color.Info" Class="mb-3" />
|
||||
<MudText Typo="Typo.h6" Class="mb-2">صداقت و شفافیت</MudText>
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary">
|
||||
در تمامی تعاملات خود با مشتریان و همکاران، صداقت و شفافیت را سرلوحه کار خود قرار دادهایم.
|
||||
</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudPaper Elevation="2" Class="pa-4 rounded-xl text-center">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Lightbulb" Size="Size.Large" Color="Color.Warning" Class="mb-3" />
|
||||
<MudText Typo="Typo.h6" Class="mb-2">نوآوری</MudText>
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary">
|
||||
همواره در پی یافتن راهکارهای جدید و بهبود فرآیندها هستیم تا بهترین تجربه را برای کاربران فراهم کنیم.
|
||||
</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudPaper Elevation="2" Class="pa-4 rounded-xl text-center">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Group" Size="Size.Large" Color="Color.Success" Class="mb-3" />
|
||||
<MudText Typo="Typo.h6" Class="mb-2">مشتریمداری</MudText>
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary">
|
||||
رضایت و موفقیت مشتریان اولویت اصلی ما است و تمامی تصمیمات خود را بر اساس نیازهای آنها میگیریم.
|
||||
</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudPaper Elevation="2" Class="pa-4 rounded-xl text-center">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Verified" Size="Size.Large" Color="Color.Primary" Class="mb-3" />
|
||||
<MudText Typo="Typo.h6" Class="mb-2">کیفیت</MudText>
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary">
|
||||
به کیفیت محصولات و خدمات خود افتخار میکنیم و همواره استانداردهای بالایی را رعایت میکنیم.
|
||||
</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudPaper Elevation="2" Class="pa-4 rounded-xl text-center">
|
||||
<MudIcon Icon="@Icons.Material.Filled.AccessTime" Size="Size.Large" Color="Color.Secondary" Class="mb-3" />
|
||||
<MudText Typo="Typo.h6" Class="mb-2">پاسخگویی</MudText>
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary">
|
||||
به سرعت به نیازهای مشتریان پاسخ میدهیم و پشتیبانی ۲۴ ساعته را فراهم کردهایم.
|
||||
</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudPaper Elevation="2" Class="pa-4 rounded-xl text-center">
|
||||
<MudIcon Icon="@Icons.Material.Filled.EmergencyRecording" Size="Size.Large" Color="Color.Error" Class="mb-3" />
|
||||
<MudText Typo="Typo.h6" Class="mb-2">پایداری</MudText>
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary">
|
||||
به دنبال ایجاد ارزش بلندمدت برای مشتریان و جامعه هستیم و به توسعه پایدار متعهد هستیم.
|
||||
</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudContainer>
|
||||
</section>
|
||||
<MudStack Spacing="8">
|
||||
|
||||
<!-- Team Section -->
|
||||
<section class="pb-12 bg-grey-50">
|
||||
<MudContainer MaxWidth="MaxWidth.Large">
|
||||
<MudText Typo="Typo.h3" Align="Align.Center" Class="mb-8">تیم ما</MudText>
|
||||
<MudGrid Spacing="4" Justify="Justify.Center">
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudPaper Elevation="3" Class="pa-4 text-center">
|
||||
<MudAvatar Size="Size.Large" Class="mb-3">
|
||||
<MudImage ObjectFit="ObjectFit.Cover"
|
||||
ObjectPosition="ObjectPosition.Center"
|
||||
Src="images/avatar3.jpg" />
|
||||
</MudAvatar>
|
||||
<MudText Typo="Typo.h6" Class="mb-1">علی رضایی</MudText>
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary mb-2">مدیرعامل</MudText>
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">
|
||||
بیش از ۱۰ سال تجربه در حوزه فناوری و مدیریت کسبوکار
|
||||
</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudPaper Elevation="3" Class="pa-4 text-center">
|
||||
<MudAvatar Size="Size.Large" Class="mb-3">
|
||||
<MudImage ObjectFit="ObjectFit.Cover"
|
||||
ObjectPosition="ObjectPosition.Center"
|
||||
Src="images/avatar4.jpg" />
|
||||
</MudAvatar>
|
||||
<MudText Typo="Typo.h6" Class="mb-1">مریم احمدی</MudText>
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary mb-2">مدیر محصول</MudText>
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">
|
||||
متخصص در طراحی تجربه کاربری و توسعه محصول
|
||||
</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudPaper Elevation="3" Class="pa-4 text-center">
|
||||
<MudAvatar Size="Size.Large" Class="mb-3">
|
||||
<MudImage ObjectFit="ObjectFit.Cover"
|
||||
ObjectPosition="ObjectPosition.Center"
|
||||
Src="images/avatar5.jpg" />
|
||||
</MudAvatar>
|
||||
<MudText Typo="Typo.h6" Class="mb-1">حسن کریمی</MudText>
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary mb-2">مدیر فنی</MudText>
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">
|
||||
مهندس نرمافزار با تخصص در معماری سیستمهای توزیعشده
|
||||
</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudContainer>
|
||||
</section>
|
||||
</MudStack>
|
||||
<!-- Mission & Vision -->
|
||||
<section class="py-12">
|
||||
<MudContainer MaxWidth="MaxWidth.Large">
|
||||
<MudText Typo="Typo.h3" Align="Align.Center" Class="mb-8">چشمانداز و مأموریت ما</MudText>
|
||||
<MudGrid Spacing="4" Justify="Justify.Center">
|
||||
<MudItem xs="12" md="6">
|
||||
<MudPaper Elevation="2" Class="pa-6 text-center h-100">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Visibility" Size="Size.Large" Color="Color.Primary" Class="mb-4" />
|
||||
<MudText Typo="Typo.h5" Class="mb-3">
|
||||
@(_visionSection?.Title ?? "چشمانداز")
|
||||
</MudText>
|
||||
@if (!string.IsNullOrWhiteSpace(_visionSection?.HtmlContent))
|
||||
{
|
||||
<MudText Typo="Typo.body1" Class="mud-text-secondary">
|
||||
@((MarkupString)_visionSection.HtmlContent)
|
||||
</MudText>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudText Typo="Typo.body1" Class="mud-text-secondary">
|
||||
تبدیل شدن به پیشروترین پلتفرم مدیریت تیمهای فروش در منطقه، با ارائه راهکارهای هوشمند و کاربرپسند برای کسبوکارهای کوچک و بزرگ.
|
||||
</MudText>
|
||||
}
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="6">
|
||||
<MudPaper Elevation="2" Class="pa-6 text-center h-100">
|
||||
<MudIcon Icon="@Icons.Material.Filled.TrackChanges" Size="Size.Large" Color="Color.Success" Class="mb-4" />
|
||||
<MudText Typo="Typo.h5" Class="mb-3">
|
||||
@(_missionSection?.Title ?? "مأموریت")
|
||||
</MudText>
|
||||
@if (!string.IsNullOrWhiteSpace(_missionSection?.HtmlContent))
|
||||
{
|
||||
<MudText Typo="Typo.body1" Class="mud-text-secondary">
|
||||
@((MarkupString)_missionSection.HtmlContent)
|
||||
</MudText>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudText Typo="Typo.body1" Class="mud-text-secondary">
|
||||
توانمندسازی کسبوکارها از طریق فناوریهای نوین، سادهسازی فرآیندهای پیچیده و ایجاد کارا بازار سلامتهای جدید برای رشد و توسعه پایدار.
|
||||
</MudText>
|
||||
}
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudContainer>
|
||||
</section>
|
||||
|
||||
<!-- Company Values -->
|
||||
<section class="py-12 bg-grey-50">
|
||||
<MudContainer MaxWidth="MaxWidth.Large">
|
||||
<MudText Typo="Typo.h3" Align="Align.Center" Class="mb-8">ارزشهای ما</MudText>
|
||||
<MudGrid Spacing="3" Justify="Justify.Center">
|
||||
@if (_valueSections.Any())
|
||||
{
|
||||
@foreach (var value in _valueSections)
|
||||
{
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudPaper Elevation="2" Class="pa-4 rounded-xl text-center">
|
||||
@if (!string.IsNullOrWhiteSpace(value.IconName))
|
||||
{
|
||||
<MudIcon Icon="@value.IconName" Size="Size.Large" Color="Color.Primary" Class="mb-3" />
|
||||
}
|
||||
<MudText Typo="Typo.h6" Class="mb-2">@value.Title</MudText>
|
||||
@if (!string.IsNullOrWhiteSpace(value.HtmlContent))
|
||||
{
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary">
|
||||
@((MarkupString)value.HtmlContent)
|
||||
</MudText>
|
||||
}
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@* Fallback: hardcoded values *@
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudPaper Elevation="2" Class="pa-4 rounded-xl text-center">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Security" Size="Size.Large" Color="Color.Info" Class="mb-3" />
|
||||
<MudText Typo="Typo.h6" Class="mb-2">صداقت و شفافیت</MudText>
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary">
|
||||
در تمامی تعاملات خود با مشتریان و همکاران، صداقت و شفافیت را سرلوحه کار خود قرار دادهایم.
|
||||
</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudPaper Elevation="2" Class="pa-4 rounded-xl text-center">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Lightbulb" Size="Size.Large" Color="Color.Warning" Class="mb-3" />
|
||||
<MudText Typo="Typo.h6" Class="mb-2">نوآوری</MudText>
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary">
|
||||
همواره در پی یافتن راهکارهای جدید و بهبود فرآیندها هستیم تا بهترین تجربه را برای کاربران فراهم کنیم.
|
||||
</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudPaper Elevation="2" Class="pa-4 rounded-xl text-center">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Group" Size="Size.Large" Color="Color.Success" Class="mb-3" />
|
||||
<MudText Typo="Typo.h6" Class="mb-2">مشتریمداری</MudText>
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary">
|
||||
رضایت و موفقیت مشتریان اولویت اصلی ما است و تمامی تصمیمات خود را بر اساس نیازهای آنها میگیریم.
|
||||
</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudPaper Elevation="2" Class="pa-4 rounded-xl text-center">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Verified" Size="Size.Large" Color="Color.Primary" Class="mb-3" />
|
||||
<MudText Typo="Typo.h6" Class="mb-2">کیفیت</MudText>
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary">
|
||||
به کیفیت محصولات و خدمات خود افتخار میکنیم و همواره استانداردهای بالایی را رعایت میکنیم.
|
||||
</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudPaper Elevation="2" Class="pa-4 rounded-xl text-center">
|
||||
<MudIcon Icon="@Icons.Material.Filled.AccessTime" Size="Size.Large" Color="Color.Secondary" Class="mb-3" />
|
||||
<MudText Typo="Typo.h6" Class="mb-2">پاسخگویی</MudText>
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary">
|
||||
به سرعت به نیازهای مشتریان پاسخ میدهیم و پشتیبانی ۲۴ ساعته را فراهم کردهایم.
|
||||
</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudPaper Elevation="2" Class="pa-4 rounded-xl text-center">
|
||||
<MudIcon Icon="@Icons.Material.Filled.EmergencyRecording" Size="Size.Large" Color="Color.Error" Class="mb-3" />
|
||||
<MudText Typo="Typo.h6" Class="mb-2">پایداری</MudText>
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary">
|
||||
به دنبال ایجاد ارزش بلندمدت برای مشتریان و جامعه هستیم و به توسعه پایدار متعهد هستیم.
|
||||
</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
}
|
||||
</MudGrid>
|
||||
</MudContainer>
|
||||
</section>
|
||||
|
||||
<!-- Team Section -->
|
||||
<section class="pb-12 bg-grey-50">
|
||||
<MudContainer MaxWidth="MaxWidth.Large">
|
||||
<MudText Typo="Typo.h3" Align="Align.Center" Class="mb-8">تیم ما</MudText>
|
||||
<MudGrid Spacing="4" Justify="Justify.Center">
|
||||
@if (_teamSections.Any())
|
||||
{
|
||||
@foreach (var member in _teamSections)
|
||||
{
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudPaper Elevation="2" Class="pa-4 text-center">
|
||||
<MudAvatar Size="Size.Large" Class="mb-3">
|
||||
@if (!string.IsNullOrWhiteSpace(member.ImagePath))
|
||||
{
|
||||
<AppImage Path="@member.ImagePath"
|
||||
ObjectFit="ObjectFit.Cover"
|
||||
ObjectPosition="ObjectPosition.Center" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudIcon Icon="@Icons.Material.Filled.Person" />
|
||||
}
|
||||
</MudAvatar>
|
||||
<MudText Typo="Typo.h6" Class="mb-1">@member.Title</MudText>
|
||||
@if (!string.IsNullOrWhiteSpace(member.Subtitle))
|
||||
{
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary mb-2">@member.Subtitle</MudText>
|
||||
}
|
||||
@if (!string.IsNullOrWhiteSpace(member.HtmlContent))
|
||||
{
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">
|
||||
@((MarkupString)member.HtmlContent)
|
||||
</MudText>
|
||||
}
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@* Fallback: hardcoded team *@
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudPaper Elevation="2" Class="pa-4 text-center">
|
||||
<MudAvatar Size="Size.Large" Class="mb-3">
|
||||
<MudImage ObjectFit="ObjectFit.Cover" ObjectPosition="ObjectPosition.Center" Src="images/avatar3.jpg" />
|
||||
</MudAvatar>
|
||||
<MudText Typo="Typo.h6" Class="mb-1">علی رضایی</MudText>
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary mb-2">مدیرعامل</MudText>
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">
|
||||
بیش از ۱۰ سال تجربه در حوزه فناوری و مدیریت کسبوکار
|
||||
</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudPaper Elevation="2" Class="pa-4 text-center">
|
||||
<MudAvatar Size="Size.Large" Class="mb-3">
|
||||
<MudImage ObjectFit="ObjectFit.Cover" ObjectPosition="ObjectPosition.Center" Src="images/avatar4.jpg" />
|
||||
</MudAvatar>
|
||||
<MudText Typo="Typo.h6" Class="mb-1">مریم احمدی</MudText>
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary mb-2">مدیر محصول</MudText>
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">
|
||||
متخصص در طراحی تجربه کاربری و توسعه محصول
|
||||
</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudPaper Elevation="2" Class="pa-4 text-center">
|
||||
<MudAvatar Size="Size.Large" Class="mb-3">
|
||||
<MudImage ObjectFit="ObjectFit.Cover" ObjectPosition="ObjectPosition.Center" Src="images/avatar5.jpg" />
|
||||
</MudAvatar>
|
||||
<MudText Typo="Typo.h6" Class="mb-1">حسن کریمی</MudText>
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary mb-2">مدیر فنی</MudText>
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">
|
||||
مهندس نرمافزار با تخصص در معماری سیستمهای توزیعشده
|
||||
</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
}
|
||||
</MudGrid>
|
||||
</MudContainer>
|
||||
</section>
|
||||
</MudStack>
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,37 @@
|
||||
using FrontOffice.Main.Utilities;
|
||||
|
||||
namespace FrontOffice.Main.Pages;
|
||||
|
||||
public partial class About
|
||||
{
|
||||
private void ScrollToContact()
|
||||
private bool _loading = true;
|
||||
private SitePageDto? _page;
|
||||
private SitePageSectionDto? _visionSection;
|
||||
private SitePageSectionDto? _missionSection;
|
||||
private List<SitePageSectionDto> _valueSections = new();
|
||||
private List<SitePageSectionDto> _teamSections = new();
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
// TODO: Implement smooth scroll to contact section
|
||||
try
|
||||
{
|
||||
_page = await SitePageService.GetByKeyAsync("about");
|
||||
|
||||
if (_page != null)
|
||||
{
|
||||
_visionSection = _page.GetSection("vision");
|
||||
_missionSection = _page.GetSection("mission");
|
||||
_valueSections = _page.GetSections("value-").ToList();
|
||||
_teamSections = _page.GetSections("team-").ToList();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Fallback: page remains null → hardcoded content will render
|
||||
}
|
||||
finally
|
||||
{
|
||||
_loading = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
@attribute [Route(RouteConstants.Blog.Index)]
|
||||
|
||||
<PageTitle>بلاگ | کارا بازار سلامت</PageTitle>
|
||||
|
||||
@* ═══════════════════════════════════════════════
|
||||
BLOG HEADER
|
||||
═══════════════════════════════════════════════ *@
|
||||
<section class="blog-hero">
|
||||
<MudContainer MaxWidth="MaxWidth.Large">
|
||||
<div class="text-center">
|
||||
<MudText Typo="Typo.h3" Class="dash-hero-name">بلاگ</MudText>
|
||||
<MudText Typo="Typo.body1" Class="dash-hero-sub mt-2">
|
||||
آخرین مطالب، آموزشها و اخبار کارا بازار سلامت
|
||||
</MudText>
|
||||
</div>
|
||||
</MudContainer>
|
||||
</section>
|
||||
|
||||
@* ═══════════════════════════════════════════════
|
||||
SEARCH + FILTERS
|
||||
═══════════════════════════════════════════════ *@
|
||||
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
||||
|
||||
<MudPaper Elevation="0" Class="pa-4 rounded-xl mb-6" Style="border:1px solid var(--mud-palette-divider);">
|
||||
<MudGrid Spacing="3" AlignItems="AlignItems.Center">
|
||||
<MudItem xs="12" sm="6" md="5">
|
||||
<MudTextField @bind-Value="_searchTerm"
|
||||
Placeholder="جستجو در مقالات..."
|
||||
Variant="Variant.Outlined"
|
||||
Adornment="Adornment.Start"
|
||||
AdornmentIcon="@Icons.Material.Filled.Search"
|
||||
Immediate="false"
|
||||
OnKeyUp="OnSearchKeyUp"
|
||||
Class="rounded-lg" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="5">
|
||||
<MudSelect T="long?" Value="_selectedCategoryId"
|
||||
Placeholder="همه دستهبندیها"
|
||||
Variant="Variant.Outlined"
|
||||
Class="rounded-lg"
|
||||
Clearable="true"
|
||||
ValueChanged="OnCategoryChanged">
|
||||
@foreach (var cat in _categories)
|
||||
{
|
||||
<MudSelectItem T="long?" Value="@cat.Id">
|
||||
@cat.Title
|
||||
@if (cat.PostCount > 0)
|
||||
{
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary mr-2" Style="display:inline;">(@cat.PostCount)</MudText>
|
||||
}
|
||||
</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="2">
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
FullWidth="true"
|
||||
Class="rounded-lg"
|
||||
StartIcon="@Icons.Material.Filled.Search"
|
||||
OnClick="SearchPosts">
|
||||
جستجو
|
||||
</MudButton>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudPaper>
|
||||
|
||||
@* ═══════════════════════════════════════════════
|
||||
BLOG POSTS GRID
|
||||
═══════════════════════════════════════════════ *@
|
||||
@if (_isLoading)
|
||||
{
|
||||
<LoadingState />
|
||||
}
|
||||
else if (!_posts.Any())
|
||||
{
|
||||
<MudPaper Elevation="0" Class="pa-12 text-center rounded-xl" Style="border:1px dashed var(--mud-palette-divider);">
|
||||
<MudIcon Icon="@Icons.Material.Outlined.Article" Size="Size.Large" Class="mud-text-secondary mb-3" Style="font-size:3.5rem;" />
|
||||
<MudText Typo="Typo.h6" Class="mud-text-secondary">مقالهای یافت نشد</MudText>
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary mt-1">
|
||||
@if (!string.IsNullOrWhiteSpace(_searchTerm) || _selectedCategoryId.HasValue)
|
||||
{
|
||||
<span>فیلترها را تغییر دهید یا عبارت دیگری جستجو کنید.</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span>بهزودی مقالات جدید منتشر خواهد شد.</span>
|
||||
}
|
||||
</MudText>
|
||||
</MudPaper>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudGrid Spacing="4">
|
||||
@foreach (var post in _posts)
|
||||
{
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudPaper Elevation="1" Class="rounded-xl blog-card-v2 cursor-pointer" Style="overflow:hidden;"
|
||||
@onclick="() => NavigateToPost(post.Slug)">
|
||||
@* Thumbnail *@
|
||||
<div class="blog-thumb" style="height:200px; background:#f0f0f0;">
|
||||
@if (!string.IsNullOrWhiteSpace(post.ThumbnailUrl))
|
||||
{
|
||||
<AppImage Path="@post.ThumbnailUrl"
|
||||
Alt="@post.Title"
|
||||
ObjectFit="ObjectFit.Cover"
|
||||
Style="width:100%; height:200px;" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<div style="height:200px; display:flex; align-items:center; justify-content:center; background:linear-gradient(135deg,#6366f1,#a78bfa);">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Article" Size="Size.Large" Style="color:rgba(255,255,255,.4);" />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@* Content *@
|
||||
<div class="pa-4">
|
||||
@if (post.Categories.Any())
|
||||
{
|
||||
<MudChip T="string" Size="Size.Small" Variant="Variant.Outlined"
|
||||
Color="Color.Primary" Class="mb-2">
|
||||
@post.Categories.First().Title
|
||||
</MudChip>
|
||||
}
|
||||
|
||||
<MudText Typo="Typo.h6" Class="mb-1 blog-title-clamp">
|
||||
@post.Title
|
||||
</MudText>
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(post.Summary))
|
||||
{
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary mb-3 blog-summary-clamp">
|
||||
@post.Summary
|
||||
</MudText>
|
||||
}
|
||||
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Class="mt-auto">
|
||||
@if (post.PublishedAt.HasValue)
|
||||
{
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">
|
||||
<MudIcon Icon="@Icons.Material.Filled.CalendarToday" Size="Size.Small" Class="ml-1" />
|
||||
@post.PublishedAt.Value.ToString("yyyy/MM/dd")
|
||||
</MudText>
|
||||
}
|
||||
<MudSpacer />
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Visibility" Size="Size.Small" Class="ml-1" />
|
||||
@post.ViewCount.ToString("N0")
|
||||
</MudText>
|
||||
</MudStack>
|
||||
</div>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
}
|
||||
</MudGrid>
|
||||
|
||||
@* ── Pagination ── *@
|
||||
@if (_totalPages > 1)
|
||||
{
|
||||
<div class="d-flex justify-center mt-8">
|
||||
<MudPagination Count="@_totalPages"
|
||||
Selected="@_currentPage"
|
||||
SelectedChanged="OnPageChanged"
|
||||
Color="Color.Primary"
|
||||
Variant="Variant.Outlined"
|
||||
Class="rounded-lg" />
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
</MudContainer>
|
||||
@@ -0,0 +1,91 @@
|
||||
using FrontOffice.Main.Utilities;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
|
||||
namespace FrontOffice.Main.Pages.Blog;
|
||||
|
||||
public partial class Index
|
||||
{
|
||||
[Inject] private BlogPostService BlogPostService { get; set; } = default!;
|
||||
[Inject] private BlogCategoryService BlogCategoryService { get; set; } = default!;
|
||||
|
||||
// ── State ──
|
||||
private List<BlogPostCardDto> _posts = new();
|
||||
private List<BlogCategoryDto> _categories = new();
|
||||
private string? _searchTerm;
|
||||
private long? _selectedCategoryId;
|
||||
private int _currentPage = 1;
|
||||
private int _totalPages;
|
||||
private bool _isLoading = true;
|
||||
private const int PageSize = 9;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
// Load categories and posts in parallel
|
||||
var categoriesTask = BlogCategoryService.GetActiveCategoriesAsync();
|
||||
var postsTask = LoadPostsAsync();
|
||||
|
||||
_categories = await categoriesTask;
|
||||
await postsTask;
|
||||
}
|
||||
|
||||
private async Task LoadPostsAsync()
|
||||
{
|
||||
_isLoading = true;
|
||||
StateHasChanged();
|
||||
|
||||
try
|
||||
{
|
||||
var result = await BlogPostService.GetPublishedPostsAsync(
|
||||
page: _currentPage,
|
||||
pageSize: PageSize,
|
||||
search: _searchTerm,
|
||||
categoryId: _selectedCategoryId);
|
||||
|
||||
_posts = result.Posts;
|
||||
_totalPages = result.TotalPages;
|
||||
}
|
||||
catch
|
||||
{
|
||||
_posts = new();
|
||||
_totalPages = 0;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isLoading = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SearchPosts()
|
||||
{
|
||||
_currentPage = 1;
|
||||
await LoadPostsAsync();
|
||||
}
|
||||
|
||||
private async Task OnSearchKeyUp(KeyboardEventArgs e)
|
||||
{
|
||||
if (e.Key == "Enter")
|
||||
{
|
||||
await SearchPosts();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OnCategoryChanged(long? categoryId)
|
||||
{
|
||||
_selectedCategoryId = categoryId;
|
||||
_currentPage = 1;
|
||||
await LoadPostsAsync();
|
||||
}
|
||||
|
||||
private async Task OnPageChanged(int page)
|
||||
{
|
||||
_currentPage = page;
|
||||
await LoadPostsAsync();
|
||||
}
|
||||
|
||||
private void NavigateToPost(string slug)
|
||||
{
|
||||
Navigation.NavigateTo($"/blog/{slug}");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
@page "/blog/{Slug}"
|
||||
|
||||
<PageTitle>@(_post?.Title ?? "مقاله") | بلاگ</PageTitle>
|
||||
|
||||
@if (_isLoading)
|
||||
{
|
||||
<MudContainer MaxWidth="MaxWidth.Medium" Class="py-12">
|
||||
<LoadingState />
|
||||
</MudContainer>
|
||||
}
|
||||
else if (_post == null)
|
||||
{
|
||||
<MudContainer MaxWidth="MaxWidth.Medium" Class="py-12">
|
||||
<EmptyState Icon="@Icons.Material.Filled.SearchOff"
|
||||
Title="مقاله یافت نشد"
|
||||
Description="این مقاله حذف شده یا آدرس اشتباه است."
|
||||
ActionText="بازگشت به بلاگ"
|
||||
ActionHref="/blog" />
|
||||
</MudContainer>
|
||||
}
|
||||
else
|
||||
{
|
||||
@* ═══ Featured Image Header ═══ *@
|
||||
@if (!string.IsNullOrWhiteSpace(_post.FeaturedImagePath))
|
||||
{
|
||||
<div class="blog-detail-hero">
|
||||
<AppImage Path="@_post.FeaturedImagePath"
|
||||
Alt="@_post.Title"
|
||||
ObjectFit="ObjectFit.Cover"
|
||||
Style="width:100%; height:100%;" />
|
||||
<div class="blog-detail-hero-overlay"></div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.Medium" Class="py-6">
|
||||
@* ── Breadcrumb ── *@
|
||||
<MudStack Row="true" Spacing="1" AlignItems="AlignItems.Center" Class="mb-4">
|
||||
<MudLink Href="/" Typo="Typo.caption" Class="mud-text-secondary">خانه</MudLink>
|
||||
<MudIcon Icon="@Icons.Material.Filled.ChevronLeft" Size="Size.Small" Class="mud-text-secondary" />
|
||||
<MudLink Href="/blog" Typo="Typo.caption" Class="mud-text-secondary">بلاگ</MudLink>
|
||||
<MudIcon Icon="@Icons.Material.Filled.ChevronLeft" Size="Size.Small" Class="mud-text-secondary" />
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">@_post.Title</MudText>
|
||||
</MudStack>
|
||||
|
||||
@* ── Article Header ── *@
|
||||
<MudPaper Elevation="0" Class="pa-0 mb-6">
|
||||
@if (_post.Categories.Any())
|
||||
{
|
||||
<MudStack Row="true" Spacing="1" Class="mb-3 flex-wrap">
|
||||
@foreach (var cat in _post.Categories)
|
||||
{
|
||||
<MudChip T="string" Size="Size.Small" Variant="Variant.Filled"
|
||||
Color="Color.Primary" Class="rounded-pill"
|
||||
OnClick="() => NavigateToCategoryFilter(cat.Id)">
|
||||
@cat.Title
|
||||
</MudChip>
|
||||
}
|
||||
</MudStack>
|
||||
}
|
||||
|
||||
<MudText Typo="Typo.h4" Class="mb-3 fw-bold" Style="line-height:1.6;">
|
||||
@_post.Title
|
||||
</MudText>
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(_post.Summary))
|
||||
{
|
||||
<MudText Typo="Typo.subtitle1" Class="mud-text-secondary mb-4" Style="line-height:1.8;">
|
||||
@_post.Summary
|
||||
</MudText>
|
||||
}
|
||||
|
||||
<MudStack Row="true" Spacing="3" AlignItems="AlignItems.Center" Class="mb-4 flex-wrap">
|
||||
@if (_post.PublishedAt.HasValue)
|
||||
{
|
||||
<MudStack Row="true" Spacing="1" AlignItems="AlignItems.Center">
|
||||
<MudIcon Icon="@Icons.Material.Filled.CalendarToday" Size="Size.Small" Color="Color.Primary" />
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary">
|
||||
@_post.PublishedAt.Value.ToString("yyyy/MM/dd")
|
||||
</MudText>
|
||||
</MudStack>
|
||||
}
|
||||
<MudStack Row="true" Spacing="1" AlignItems="AlignItems.Center">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Visibility" Size="Size.Small" Color="Color.Primary" />
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary">
|
||||
@_post.ViewCount.ToString("N0") بازدید
|
||||
</MudText>
|
||||
</MudStack>
|
||||
</MudStack>
|
||||
|
||||
<MudDivider Class="mb-6" />
|
||||
</MudPaper>
|
||||
|
||||
@* ── Article Content ── *@
|
||||
<MudPaper Elevation="0" Class="blog-content mb-8">
|
||||
@((MarkupString)_post.HtmlContent)
|
||||
</MudPaper>
|
||||
|
||||
@* ── Tags ── *@
|
||||
@if (_post.Tags.Any())
|
||||
{
|
||||
<MudDivider Class="mb-4" />
|
||||
<MudStack Row="true" Spacing="1" AlignItems="AlignItems.Center" Class="mb-6 flex-wrap">
|
||||
<MudIcon Icon="@Icons.Material.Filled.LocalOffer" Size="Size.Small" Class="mud-text-secondary ml-2" />
|
||||
@foreach (var tag in _post.Tags)
|
||||
{
|
||||
<MudChip T="string" Size="Size.Small" Variant="Variant.Outlined"
|
||||
Class="rounded-pill">
|
||||
@tag.Title
|
||||
</MudChip>
|
||||
}
|
||||
</MudStack>
|
||||
}
|
||||
|
||||
@* ── Back to Blog ── *@
|
||||
<div class="text-center mt-6 mb-4">
|
||||
<MudButton Variant="Variant.Outlined" Color="Color.Primary" OnClick="GoBack"
|
||||
Class="rounded-pill" StartIcon="@Icons.Material.Filled.ArrowForward">
|
||||
بازگشت به بلاگ
|
||||
</MudButton>
|
||||
</div>
|
||||
</MudContainer>
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using FrontOffice.Main.Utilities;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.JSInterop;
|
||||
|
||||
namespace FrontOffice.Main.Pages.Blog;
|
||||
|
||||
public partial class Post
|
||||
{
|
||||
[Inject] private IJSRuntime JS { get; set; } = default!;
|
||||
[Parameter] public string Slug { get; set; } = string.Empty;
|
||||
|
||||
[Inject] private BlogPostService BlogPostService { get; set; } = default!;
|
||||
|
||||
private BlogPostDetailDto? _post;
|
||||
private bool _isLoading = true;
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Slug)) return;
|
||||
|
||||
_isLoading = true;
|
||||
StateHasChanged();
|
||||
|
||||
try
|
||||
{
|
||||
_post = await BlogPostService.GetBySlugAsync(Slug);
|
||||
|
||||
// Fire-and-forget: increment view count
|
||||
if (_post != null)
|
||||
{
|
||||
_ = BlogPostService.IncrementViewCountAsync(_post.Id);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
_post = null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isLoading = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void NavigateToCategoryFilter(long categoryId)
|
||||
{
|
||||
Navigation.NavigateTo($"/blog?category={categoryId}");
|
||||
}
|
||||
|
||||
private async Task GoBack() => await JS.InvokeVoidAsync("history.back");
|
||||
}
|
||||
@@ -5,10 +5,11 @@
|
||||
<PageTitle>تکمیل خرید</PageTitle>
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
||||
<PageHeader Title="تکمیل خرید" BackHref="@RouteConstants.Package.List" />
|
||||
<MudGrid Spacing="4">
|
||||
<!-- Header -->
|
||||
<MudItem xs="12">
|
||||
<MudPaper Elevation="4" Class="pa-6">
|
||||
<MudPaper Elevation="2" Class="pa-4 rounded-lg">
|
||||
<MudStack Spacing="3">
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="3">
|
||||
<MudIcon Icon="@Icons.Material.Filled.ShoppingCart" Size="Size.Large" Color="Color.Primary" />
|
||||
@@ -23,7 +24,7 @@
|
||||
<MudItem xs="12" md="8">
|
||||
<MudStack>
|
||||
<!-- Package Details -->
|
||||
<MudPaper Elevation="4" Class="pa-6">
|
||||
<MudPaper Elevation="2" Class="pa-4 rounded-lg">
|
||||
<MudText Typo="Typo.h5" Class="mb-4">جزئیات پکیج</MudText>
|
||||
|
||||
@if (_selectedPackage != null)
|
||||
@@ -33,9 +34,9 @@
|
||||
<MudGrid Spacing="3">
|
||||
<MudItem xs="12" md="4">
|
||||
<MudPaper Class="pa-2 rounded-xl" Style="background: radial-gradient(600px 280px at 120% 0, #daccff 0, transparent 60%), radial-gradient(600px 280px at -10% 100%, #ffe2f2 0, transparent 60%), linear-gradient(180deg, #fff, #fbfaff);">
|
||||
<MudImage Src="@_selectedPackage.Image"
|
||||
<AppImage Path="@_selectedPackage.Image"
|
||||
Alt="@_selectedPackage.Title"
|
||||
Height="150"
|
||||
ImgHeight="150"
|
||||
ObjectFit="ObjectFit.Cover"
|
||||
ObjectPosition="ObjectPosition.Center"
|
||||
Style="width:100%"
|
||||
@@ -67,7 +68,7 @@
|
||||
</MudPaper>
|
||||
|
||||
<!-- Address Selection -->
|
||||
<MudPaper Elevation="4" Class="pa-6">
|
||||
<MudPaper Elevation="2" Class="pa-4 rounded-lg">
|
||||
<MudText Typo="Typo.h5" Class="mb-4">انتخاب آدرس</MudText>
|
||||
|
||||
@if (_isLoadingAddresses)
|
||||
@@ -149,7 +150,7 @@
|
||||
<MudItem xs="12" md="4">
|
||||
<MudStack Spacing="4">
|
||||
<!-- Discount Code Section -->
|
||||
<MudPaper Elevation="4" Class="pa-6">
|
||||
<MudPaper Elevation="2" Class="pa-4 rounded-lg">
|
||||
<MudText Typo="Typo.h5" Class="mb-4">کد تخفیف</MudText>
|
||||
<MudStack Spacing="3">
|
||||
<MudTextField @bind-Value="_discountCode"
|
||||
@@ -176,7 +177,7 @@
|
||||
<!-- Order Summary -->
|
||||
@if (_selectedPackage != null)
|
||||
{
|
||||
<MudPaper Elevation="4" Class="pa-6">
|
||||
<MudPaper Elevation="2" Class="pa-4 rounded-lg">
|
||||
<MudText Typo="Typo.h5" Class="mb-4">خلاصه سفارش</MudText>
|
||||
|
||||
<MudStack Spacing="3">
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
using FrontOffice.BFF.Package.Protobuf.Protos.Package;
|
||||
using FrontOffice.BFF.Transaction.Protobuf.Protos.Transaction;
|
||||
using FrontOffice.BFF.UserAddress.Protobuf.Protos.UserAddress;
|
||||
using FrontOffice.BFF.UserOrder.Protobuf.Protos.UserOrder;
|
||||
|
||||
using CMSMicroservice.Protobuf.Protos.Package;
|
||||
using CMSMicroservice.Protobuf.Protos.Transactions;
|
||||
using CMSMicroservice.Protobuf.Protos.UserAddress;
|
||||
using CMSMicroservice.Protobuf.Protos.UserOrder;
|
||||
using FrontOffice.Main.Utilities;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
@@ -15,13 +16,13 @@ public partial class Checkout
|
||||
[Inject] private PackageContract.PackageContractClient PackageClient { get; set; } = default!;
|
||||
[Inject] private UserAddressContract.UserAddressContractClient UserAddressContract { get; set; } = default!;
|
||||
[Inject] private UserOrderContract.UserOrderContractClient UserOrderContract { get; set; } = default!;
|
||||
[Inject] private TransactionContract.TransactionContractClient TransactionContract { get; set; } = default!;
|
||||
[Inject] private TransactionsContract.TransactionsContractClient TransactionContract { get; set; } = default!;
|
||||
|
||||
[Parameter] public long? PackageId { get; set; }
|
||||
|
||||
private Pack? _selectedPackage;
|
||||
private List<GetAllUserAddressByFilterResponseModel> _addresses = new();
|
||||
private GetAllUserAddressByFilterResponseModel? _selectedAddress;
|
||||
private List<CustomerAddressModel> _addresses = new();
|
||||
private CustomerAddressModel? _selectedAddress;
|
||||
private bool _isLoadingAddresses;
|
||||
private bool _isProcessingPayment;
|
||||
|
||||
@@ -58,7 +59,7 @@ public partial class Checkout
|
||||
Id: response.Id,
|
||||
Title: response.Title,
|
||||
Body: response.Description,
|
||||
Image: UrlUtility.DownloadUrl + response.ImagePath,
|
||||
Image: response.ImagePath ?? string.Empty,
|
||||
Price: response.Price
|
||||
);
|
||||
_finalPrice = _selectedPackage.Price;
|
||||
@@ -76,7 +77,7 @@ public partial class Checkout
|
||||
_isLoadingAddresses = true;
|
||||
try
|
||||
{
|
||||
var response = await UserAddressContract.GetAllUserAddressByFilterAsync(request: new());
|
||||
var response = await UserAddressContract.GetCustomerAddressesAsync(request: new());
|
||||
if (response?.Models?.Any() == true)
|
||||
{
|
||||
_addresses = response.Models.ToList();
|
||||
@@ -85,13 +86,13 @@ public partial class Checkout
|
||||
}
|
||||
else
|
||||
{
|
||||
_addresses = new List<GetAllUserAddressByFilterResponseModel>();
|
||||
_addresses = new List<CustomerAddressModel>();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"خطا در بارگذاری آدرسها: {ex.Message}", Severity.Error);
|
||||
_addresses = new List<GetAllUserAddressByFilterResponseModel>();
|
||||
_addresses = new List<CustomerAddressModel>();
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -186,7 +187,7 @@ public partial class Checkout
|
||||
try
|
||||
{
|
||||
// Step 1: Create payment request
|
||||
var paymentRequest = new PaymentRequestRequest
|
||||
var paymentRequest = new CustomerPaymentRequestRequest
|
||||
{
|
||||
Amount = _finalPrice,
|
||||
CallbackUrl = $"{Navigation.BaseUri}checkout/callback",
|
||||
@@ -195,7 +196,7 @@ public partial class Checkout
|
||||
Type = TransactionTypeEnum.Real
|
||||
};
|
||||
|
||||
var paymentResponse = await TransactionContract.PaymentRequestAsync(paymentRequest);
|
||||
var paymentResponse = await TransactionContract.CustomerPaymentRequestAsync(paymentRequest);
|
||||
|
||||
if (string.IsNullOrEmpty(paymentResponse.PaymentGWUrl))
|
||||
Snackbar.Add("آدرس درگاه پرداخت دریافت نشد.", Severity.Error);
|
||||
@@ -203,9 +204,9 @@ public partial class Checkout
|
||||
// Step 2: Create user order
|
||||
var orderRequest = new CreateNewUserOrderRequest
|
||||
{
|
||||
Price = _finalPrice,
|
||||
Amount = _finalPrice,
|
||||
PackageId = _selectedPackage.Id,
|
||||
PaymentStatus = false // Will be updated after payment verification
|
||||
PaymentStatus = CMSMicroservice.Protobuf.Protos.PaymentStatus.Pending
|
||||
};
|
||||
|
||||
var orderResponse = await UserOrderContract.CreateNewUserOrderAsync(orderRequest);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using FrontOffice.Main.Utilities;
|
||||
using FrontOffice.BFF.User.Protobuf.Protos.User;
|
||||
using CMSMicroservice.Protobuf.Protos.User;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
|
||||
|
||||
@@ -3,15 +3,13 @@
|
||||
@using MudBlazor
|
||||
@inject ClubConfigurationService ClubConfigService
|
||||
@inject IDialogService DialogService
|
||||
@inject IJSRuntime JS
|
||||
|
||||
<PageTitle>ویژگیهای باشگاه مشتریان</PageTitle>
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
||||
<MudStack Spacing="3">
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
||||
<MudText Typo="Typo.h5">ویژگیهای باشگاه مشتریان</MudText>
|
||||
<MudButton Variant="Variant.Text" StartIcon="@Icons.Material.Filled.ArrowBack" Href="@RouteConstants.Club.Membership">بازگشت</MudButton>
|
||||
</MudStack>
|
||||
<PageHeader Title="ویژگیهای باشگاه مشتریان" BackHref="@RouteConstants.Club.Membership" />
|
||||
|
||||
<!-- توضیحات کلی باشگاه -->
|
||||
<MudPaper Elevation="2" Class="pa-4 rounded-lg">
|
||||
@@ -30,13 +28,13 @@
|
||||
|
||||
@if (_isLoading)
|
||||
{
|
||||
<MudProgressLinear Color="Color.Primary" Indeterminate="true" />
|
||||
<LoadingState Message="در حال دریافت ویژگیها..." />
|
||||
}
|
||||
else if (_features == null || !_features.Any())
|
||||
{
|
||||
<MudAlert Severity="Severity.Info">
|
||||
هنوز ویژگیای برای شما فعال نشده است.
|
||||
</MudAlert>
|
||||
<EmptyState Icon="@Icons.Material.Filled.Stars"
|
||||
Title="هنوز ویژگیای برای شما فعال نشده است"
|
||||
Description="با خرید پکیج یا ارتقاء عضویت، ویژگیهای اختصاصی فعال میشوند." />
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -73,7 +71,7 @@
|
||||
FullWidth="true"
|
||||
Size="Size.Large"
|
||||
StartIcon="@Icons.Material.Filled.ArrowBack"
|
||||
Href="@RouteConstants.Club.Membership">
|
||||
OnClick="GoBack">
|
||||
بازگشت به صفحه باشگاه
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
@@ -190,4 +188,6 @@
|
||||
_selectedFeature = feature;
|
||||
_showDetailDialog = true;
|
||||
}
|
||||
|
||||
private async Task GoBack() => await JS.InvokeVoidAsync("history.back");
|
||||
}
|
||||
|
||||
@@ -7,14 +7,11 @@
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
||||
<MudStack Spacing="3">
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
||||
<MudText Typo="Typo.h5">عضویت باشگاه مشتریان</MudText>
|
||||
<MudButton Variant="Variant.Text" StartIcon="@Icons.Material.Filled.ArrowBack" Href="@RouteConstants.Profile.Index">بازگشت</MudButton>
|
||||
</MudStack>
|
||||
<PageHeader Title="عضویت باشگاه مشتریان" BackHref="@RouteConstants.Profile.Index" />
|
||||
|
||||
@if (_isLoading)
|
||||
{
|
||||
<MudProgressLinear Color="Color.Primary" Indeterminate="true" />
|
||||
<LoadingState Message="در حال دریافت اطلاعات عضویت..." />
|
||||
}
|
||||
else if (_membership is not null)
|
||||
{
|
||||
@@ -56,19 +53,6 @@
|
||||
<MudAlert Severity="Severity.Warning" Variant="Variant.Outlined">
|
||||
<MudText>شما هنوز عضو باشگاه مشتریان نیستید. برای استفاده از مزایای ویژه، اکنون فعال کنید!</MudText>
|
||||
</MudAlert>
|
||||
|
||||
@if (_clubConfig != null)
|
||||
{
|
||||
<MudPaper Elevation="0" Class="pa-3 mt-3 rounded-lg" Style="background-color: var(--mud-palette-info-lighten);">
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
||||
<MudStack Spacing="1">
|
||||
<MudText Typo="Typo.subtitle2" Color="Color.Info">هزینه عضویت در باشگاه مشتریان</MudText>
|
||||
<MudText Typo="Typo.caption">شامل @((_clubConfig.MembershipGiftValue / 10000).ToString("N0")) تومان هدیه</MudText>
|
||||
</MudStack>
|
||||
<MudText Typo="Typo.h6" Color="Color.Info">@((_clubConfig.ActivationFee / 10000).ToString("N0")) تومان</MudText>
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
}
|
||||
}
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
@@ -103,11 +87,6 @@
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
|
||||
<!-- فعالسازی یا تمدید عضویت -->
|
||||
@if (!_membership.IsActive || (_membership.DaysRemaining.HasValue && _membership.DaysRemaining.Value <= 30))
|
||||
{
|
||||
<ActivationSection OnActivationSuccess="HandleActivationSuccess" />
|
||||
}
|
||||
|
||||
<!-- دکمه مشاهده ویژگیهای بیشتر -->
|
||||
<MudButton Variant="Variant.Outlined"
|
||||
|
||||
@@ -8,10 +8,7 @@
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
||||
<MudStack Spacing="3">
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
||||
<MudText Typo="Typo.h5">پاداشهای من</MudText>
|
||||
<MudButton Variant="Variant.Text" StartIcon="@Icons.Material.Filled.ArrowBack" Href="@RouteConstants.Profile.Index">بازگشت</MudButton>
|
||||
</MudStack>
|
||||
<PageHeader Title="پاداشهای من" BackHref="@RouteConstants.Profile.Index" />
|
||||
|
||||
<!-- فیلترها -->
|
||||
<MudPaper Elevation="2" Class="pa-3 rounded-lg">
|
||||
@@ -52,7 +49,7 @@
|
||||
|
||||
@if (_isLoading)
|
||||
{
|
||||
<MudProgressLinear Color="Color.Primary" Indeterminate="true" />
|
||||
<LoadingState Message="در حال دریافت پاداشها..." />
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -7,10 +7,7 @@
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
||||
<MudStack Spacing="3">
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
||||
<MudText Typo="Typo.h5">گزارش هفتگی پاداش</MudText>
|
||||
<MudButton Variant="Variant.Text" StartIcon="@Icons.Material.Filled.ArrowBack" Href="@RouteConstants.Commission.Dashboard">بازگشت</MudButton>
|
||||
</MudStack>
|
||||
<PageHeader Title="گزارش هفتگی پاداش" BackHref="@RouteConstants.Commission.Dashboard" />
|
||||
|
||||
<!-- انتخاب هفته -->
|
||||
<MudPaper Elevation="2" Class="pa-3 rounded-lg">
|
||||
@@ -50,7 +47,7 @@
|
||||
|
||||
@if (_isLoading)
|
||||
{
|
||||
<MudProgressLinear Color="Color.Primary" Indeterminate="true" />
|
||||
<LoadingState Message="در حال دریافت گزارش..." />
|
||||
}
|
||||
else if (_weeklyBalance is not null)
|
||||
{
|
||||
@@ -76,11 +73,11 @@
|
||||
<MudGrid Spacing="2">
|
||||
|
||||
<MudItem xs="6">
|
||||
<MudPaper Elevation="3" Class="pa-4 rounded-lg text-center" Style="background: linear-gradient(135deg, #e8f5e9 0%, #c8e6c9 100%);">
|
||||
<MudPaper Elevation="2" Class="pa-4 rounded-lg text-center bg-success-soft stat-card-ds">
|
||||
<MudStack Spacing="1" AlignItems="AlignItems.Center">
|
||||
<MudIcon Icon="@Icons.Material.Filled.ChevronRight" Color="Color.Success" Size="Size.Large" />
|
||||
<MudText Typo="Typo.subtitle2" Color="Color.Success">تیم دوم</MudText>
|
||||
<MudText Typo="Typo.h5" Color="Color.Success" Style="font-weight: bold;">@_weeklyBalance.RightBalanceFormatted</MudText>
|
||||
<MudText Typo="Typo.h5" Color="Color.Success" Class="dash-stat-value">@_weeklyBalance.RightBalanceFormatted</MudText>
|
||||
<MudProgressLinear Color="Color.Success"
|
||||
Value="@GetRightPercentage()"
|
||||
Size="Size.Medium"
|
||||
@@ -93,11 +90,11 @@
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
<MudItem xs="6">
|
||||
<MudPaper Elevation="3" Class="pa-4 rounded-lg text-center" Style="background: linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%);">
|
||||
<MudPaper Elevation="2" Class="pa-4 rounded-lg text-center bg-info-soft stat-card-ds">
|
||||
<MudStack Spacing="1" AlignItems="AlignItems.Center">
|
||||
<MudIcon Icon="@Icons.Material.Filled.ChevronLeft" Color="Color.Info" Size="Size.Large"/>
|
||||
<MudText Typo="Typo.subtitle2" Color="Color.Info">تیم اول</MudText>
|
||||
<MudText Typo="Typo.h5" Color="Color.Info" Style="font-weight: bold;">@_weeklyBalance.LeftBalanceFormatted</MudText>
|
||||
<MudText Typo="Typo.h5" Color="Color.Info" Class="dash-stat-value">@_weeklyBalance.LeftBalanceFormatted</MudText>
|
||||
<MudProgressLinear Color="Color.Info"
|
||||
Value="@GetLeftPercentage()"
|
||||
Size="Size.Medium"
|
||||
@@ -114,7 +111,7 @@
|
||||
|
||||
<!-- محاسبات پاداش - فشردهتر -->
|
||||
<MudPaper Elevation="2" Class="pa-4 rounded-lg">
|
||||
<MudText Typo="Typo.subtitle1" Class="mb-2" Style="font-weight: bold;">محاسبات پاداش</MudText>
|
||||
<MudText Typo="Typo.subtitle1" Class="mb-2 fw-bold">محاسبات پاداش</MudText>
|
||||
<MudSimpleTable Dense="true" Hover="true" Style="background: transparent;">
|
||||
<tbody>
|
||||
<tr>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
@attribute [Route(RouteConstants.Contact.Index)]
|
||||
@inject SitePageService SitePageService
|
||||
|
||||
<PageTitle>ارتباط با ما | کارا بازار سلامت</PageTitle>
|
||||
|
||||
@@ -8,10 +9,10 @@
|
||||
<MudStack AlignItems="AlignItems.Center" Spacing="4">
|
||||
<MudChip T="string" Color="Color.Secondary" Variant="Variant.Filled" Class="mb-2">ارتباط با ما</MudChip>
|
||||
<MudText Typo="Typo.h2" Align="Align.Center" Class="mb-3">
|
||||
آماده شنیدن صدای شما هستیم
|
||||
@(_page?.HeroTitle ?? "آماده شنیدن صدای شما هستیم")
|
||||
</MudText>
|
||||
<MudText Typo="Typo.body1" Align="Align.Center" Class="mud-text-secondary mb-6" Style="max-width:600px">
|
||||
سوالات، پیشنهادات یا انتقادات خود را با ما در میان بگذارید. تیم ما آماده پاسخگویی به شماست.
|
||||
@(_page?.HeroSubtitle ?? "سوالات، پیشنهادات یا انتقادات خود را با ما در میان بگذارید. تیم ما آماده پاسخگویی به شماست.")
|
||||
</MudText>
|
||||
</MudStack>
|
||||
</MudContainer>
|
||||
@@ -25,7 +26,7 @@
|
||||
<MudGrid Spacing="6">
|
||||
<!-- Contact Form -->
|
||||
<MudItem xs="12" lg="8">
|
||||
<MudPaper Elevation="3" Class="pa-6">
|
||||
<MudPaper Elevation="2" Class="pa-6">
|
||||
<MudText Typo="Typo.h5" Class="mb-6">فرم تماس</MudText>
|
||||
|
||||
<MudForm @ref="_form" Model="_contactForm">
|
||||
@@ -72,12 +73,12 @@
|
||||
Variant="Variant.Outlined"
|
||||
Required="true"
|
||||
RequiredError="انتخاب موضوع الزامی است.">
|
||||
<MudSelectItem Value="@("general")">عمومی</MudSelectItem>
|
||||
<MudSelectItem Value="@("support")">پشتیبانی فنی</MudSelectItem>
|
||||
<MudSelectItem Value="@("sales")">فروش و قیمتگذاری</MudSelectItem>
|
||||
<MudSelectItem Value="@("partnership")">شریک تجاری</MudSelectItem>
|
||||
<MudSelectItem Value="@("complaint")">شکایت</MudSelectItem>
|
||||
<MudSelectItem Value="@("suggestion")">پیشنهاد</MudSelectItem>
|
||||
<MudSelectItem T="string" Value="@("general")">عمومی</MudSelectItem>
|
||||
<MudSelectItem T="string" Value="@("support")">پشتیبانی فنی</MudSelectItem>
|
||||
<MudSelectItem T="string" Value="@("sales")">فروش و قیمتگذاری</MudSelectItem>
|
||||
<MudSelectItem T="string" Value="@("partnership")">شریک تجاری</MudSelectItem>
|
||||
<MudSelectItem T="string" Value="@("complaint")">شکایت</MudSelectItem>
|
||||
<MudSelectItem T="string" Value="@("suggestion")">پیشنهاد</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
|
||||
@@ -123,7 +124,7 @@
|
||||
<MudItem xs="12" lg="4">
|
||||
<MudStack Spacing="4">
|
||||
<!-- Contact Details -->
|
||||
<MudPaper Elevation="3" Class="pa-6">
|
||||
<MudPaper Elevation="2" Class="pa-6">
|
||||
<MudText Typo="Typo.h6" Class="mb-4">اطلاعات تماس</MudText>
|
||||
|
||||
<MudStack Spacing="4">
|
||||
@@ -132,7 +133,7 @@
|
||||
<div>
|
||||
<MudText Typo="Typo.body2" >آدرس</MudText>
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">
|
||||
کرج مهرویلا میدان مادر ساختمان بزرگمهر طبقه ۴ واحد ۱۶
|
||||
@(_contactAddress ?? "کرج مهرویلا میدان مادر ساختمان بزرگمهر طبقه ۴ واحد ۱۶")
|
||||
</MudText>
|
||||
</div>
|
||||
</MudStack>
|
||||
@@ -141,7 +142,7 @@
|
||||
<MudIcon Icon="@Icons.Material.Filled.Phone" Color="Color.Success" Size="Size.Large" />
|
||||
<div>
|
||||
<MudText Typo="Typo.body2" >تلفن</MudText>
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">026-34233563</MudText>
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">@(_contactPhone ?? "026-34233563")</MudText>
|
||||
</div>
|
||||
</MudStack>
|
||||
|
||||
@@ -149,7 +150,7 @@
|
||||
<MudIcon Icon="@Icons.Material.Filled.Email" Color="Color.Info" Size="Size.Large" />
|
||||
<div>
|
||||
<MudText Typo="Typo.body2" >ایمیل</MudText>
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">info@kbs1.co</MudText>
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">@(_contactEmail ?? "info@kbs1.co")</MudText>
|
||||
</div>
|
||||
</MudStack>
|
||||
|
||||
@@ -158,8 +159,7 @@
|
||||
<div>
|
||||
<MudText Typo="Typo.body2" >ساعات کاری</MudText>
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">
|
||||
شنبه تا پنجشنبه<br />
|
||||
۹ صبح تا ۶ عصر
|
||||
@((MarkupString)(_contactHours ?? "شنبه تا پنجشنبه<br />۹ صبح تا ۶ عصر"))
|
||||
</MudText>
|
||||
</div>
|
||||
</MudStack>
|
||||
@@ -167,14 +167,14 @@
|
||||
</MudPaper>
|
||||
|
||||
<!-- Social Media -->
|
||||
<MudPaper Elevation="3" Class="pa-6">
|
||||
<MudPaper Elevation="2" Class="pa-6">
|
||||
<MudText Typo="Typo.h6" Class="mb-4">شبکههای اجتماعی</MudText>
|
||||
|
||||
<MudStack Spacing="3">
|
||||
<MudButton Variant="Variant.Outlined"
|
||||
Color="Color.Inherit"
|
||||
StartIcon="@Icons.Custom.Brands.Telegram"
|
||||
Href="https://t.me/kbs1"
|
||||
Href="@(_socialTelegram ?? "https://t.me/kbs1")"
|
||||
Target="_blank"
|
||||
FullWidth="true">
|
||||
تلگرام
|
||||
@@ -183,7 +183,7 @@
|
||||
<MudButton Variant="Variant.Outlined"
|
||||
Color="Color.Inherit"
|
||||
StartIcon="@Icons.Custom.Brands.Instagram"
|
||||
Href="https://instagram.com/kbs1"
|
||||
Href="@(_socialInstagram ?? "https://instagram.com/kbs1")"
|
||||
Target="_blank"
|
||||
FullWidth="true">
|
||||
اینستاگرام
|
||||
@@ -192,7 +192,7 @@
|
||||
<MudButton Variant="Variant.Outlined"
|
||||
Color="Color.Inherit"
|
||||
StartIcon="@Icons.Custom.Brands.LinkedIn"
|
||||
Href="https://linkedin.com/company/kbs1"
|
||||
Href="@(_socialLinkedin ?? "https://linkedin.com/company/kbs1")"
|
||||
Target="_blank"
|
||||
FullWidth="true">
|
||||
لینکدین
|
||||
@@ -201,7 +201,7 @@
|
||||
<MudButton Variant="Variant.Outlined"
|
||||
Color="Color.Inherit"
|
||||
StartIcon="@Icons.Custom.Brands.WhatsApp"
|
||||
Href="https://wa.me/989123456789"
|
||||
Href="@(_socialWhatsapp ?? "https://wa.me/989123456789")"
|
||||
Target="_blank"
|
||||
FullWidth="true">
|
||||
واتساپ
|
||||
@@ -219,14 +219,14 @@
|
||||
<MudContainer MaxWidth="MaxWidth.Large">
|
||||
<MudText Typo="Typo.h4" Align="Align.Center" Class="mb-8">موقعیت مکانی</MudText>
|
||||
|
||||
<MudPaper Elevation="3" Class="pa-4 overflow-hidden">
|
||||
<MudPaper Elevation="2" Class="pa-4 overflow-hidden">
|
||||
<!-- Placeholder for Map -->
|
||||
<div style="height: 400px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); display: flex; align-items: center; justify-content: center;">
|
||||
<div class="contact-map-placeholder" style="height: 400px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); display: flex; align-items: center; justify-content: center; border-radius: 12px;">
|
||||
<MudStack AlignItems="AlignItems.Center" Spacing="3">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Map" Size="Size.Large" Style="color: white;" />
|
||||
<MudText Typo="Typo.h6" Style="color: white;">نقشه موقعیت مکانی</MudText>
|
||||
<MudText Typo="Typo.body2" Style="color: rgba(255,255,255,0.8);" Align="Align.Center">
|
||||
کرج مهرویلا میدان مادر ساختمان بزرگمهر طبقه ۴ واحد ۱۶
|
||||
@(_contactAddress ?? "کرج مهرویلا میدان مادر ساختمان بزرگمهر طبقه ۴ واحد ۱۶")
|
||||
</MudText>
|
||||
</MudStack>
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
using FluentValidation;
|
||||
using FrontOffice.Main.Utilities;
|
||||
using MudBlazor;
|
||||
using Severity = MudBlazor.Severity;
|
||||
|
||||
namespace FrontOffice.Main.Pages;
|
||||
|
||||
public partial class Contact
|
||||
{
|
||||
private ContactForm _contactForm = new();
|
||||
@@ -10,6 +12,60 @@ public partial class Contact
|
||||
private bool _isSubmitting;
|
||||
private readonly ContactFormValidator _contactFormValidator = new();
|
||||
|
||||
// Dynamic CMS data
|
||||
private SitePageDto? _page;
|
||||
private string? _contactAddress;
|
||||
private string? _contactPhone;
|
||||
private string? _contactEmail;
|
||||
private string? _contactHours;
|
||||
private string? _socialTelegram;
|
||||
private string? _socialInstagram;
|
||||
private string? _socialLinkedin;
|
||||
private string? _socialWhatsapp;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
_page = await SitePageService.GetByKeyAsync("contact");
|
||||
|
||||
if (_page != null)
|
||||
{
|
||||
// Load contact info from CMS section
|
||||
var contactInfoSection = _page.GetSection("contact-info");
|
||||
if (contactInfoSection != null)
|
||||
{
|
||||
var contactData = contactInfoSection.GetExtraData<ContactInfoData>();
|
||||
if (contactData != null)
|
||||
{
|
||||
_contactAddress = contactData.Address;
|
||||
_contactPhone = contactData.Phone;
|
||||
_contactEmail = contactData.Email;
|
||||
_contactHours = contactData.Hours;
|
||||
}
|
||||
}
|
||||
|
||||
// Load social media links from CMS section
|
||||
var socialSection = _page.GetSection("social-media");
|
||||
if (socialSection != null)
|
||||
{
|
||||
var socialData = socialSection.GetExtraData<SocialMediaData>();
|
||||
if (socialData != null)
|
||||
{
|
||||
_socialTelegram = socialData.Telegram;
|
||||
_socialInstagram = socialData.Instagram;
|
||||
_socialLinkedin = socialData.Linkedin;
|
||||
_socialWhatsapp = socialData.Whatsapp;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Fallback: all values remain null → hardcoded defaults will render
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SubmitContactForm()
|
||||
{
|
||||
if (_form is null) return;
|
||||
@@ -55,14 +111,12 @@ public partial class Contact
|
||||
|
||||
private void CallSupport()
|
||||
{
|
||||
// TODO: Initiate phone call or show phone number
|
||||
Snackbar.Add("شماره تماس: ۰۲۱-۱۲۳۴۵۶۷۸", Severity.Info);
|
||||
Snackbar.Add($"شماره تماس: {_contactPhone ?? "026-34233563"}", Severity.Info);
|
||||
}
|
||||
|
||||
private void SendEmail()
|
||||
{
|
||||
// TODO: Open email client or redirect to email page
|
||||
Snackbar.Add("ایمیل: info@kbs1.co", Severity.Info);
|
||||
Snackbar.Add($"ایمیل: {_contactEmail ?? "info@kbs1.co"}", Severity.Info);
|
||||
}
|
||||
|
||||
public class ContactForm
|
||||
@@ -87,4 +141,21 @@ public partial class Contact
|
||||
RuleFor(x => x.Message).NotEmpty().MinimumLength(10).WithMessage("پیام باید حداقل ۱۰ کاراکتر باشد");
|
||||
}
|
||||
}
|
||||
|
||||
// DTO classes for ExtraData JSON deserialization
|
||||
private class ContactInfoData
|
||||
{
|
||||
public string? Address { get; set; }
|
||||
public string? Phone { get; set; }
|
||||
public string? Email { get; set; }
|
||||
public string? Hours { get; set; }
|
||||
}
|
||||
|
||||
private class SocialMediaData
|
||||
{
|
||||
public string? Telegram { get; set; }
|
||||
public string? Instagram { get; set; }
|
||||
public string? Linkedin { get; set; }
|
||||
public string? Whatsapp { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
@attribute [Route(RouteConstants.DiscountStore.Cart)]
|
||||
|
||||
<PageTitle>سبد خرید تخفیفی</PageTitle>
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
||||
<MudStack Spacing="3">
|
||||
<PageHeader Title="سبد خرید تخفیفی" BackHref="@RouteConstants.DiscountStore.Products" />
|
||||
|
||||
@if (DiscountCart.Items.Count == 0)
|
||||
{
|
||||
<MudAlert Severity="Severity.Info">سبد خرید تخفیفی شما خالی است.</MudAlert>
|
||||
<MudButton Variant="Variant.Outlined" StartIcon="@Icons.Material.Filled.ArrowBack"
|
||||
OnClick="() => Navigation.NavigateTo(RouteConstants.DiscountStore.Products)">
|
||||
بازگشت به فروشگاه تخفیفی
|
||||
</MudButton>
|
||||
}
|
||||
else
|
||||
{
|
||||
<!-- Desktop Table -->
|
||||
<MudHidden Breakpoint="Breakpoint.MdAndUp" Invert="true">
|
||||
<MudPaper Elevation="1" Class="pa-4 rounded-lg">
|
||||
<MudTable Items="DiscountCart.Items">
|
||||
<HeaderContent>
|
||||
<MudTh>محصول</MudTh>
|
||||
<MudTh>قیمت واحد</MudTh>
|
||||
<MudTh>سقف تخفیف</MudTh>
|
||||
<MudTh>تعداد</MudTh>
|
||||
<MudTh>تخفیف</MudTh>
|
||||
<MudTh>قیمت نهایی</MudTh>
|
||||
<MudTh></MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd>
|
||||
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
|
||||
<AppImage Path="@GetImageUrl(context.ImageUrl)" Alt="@context.Title"
|
||||
ImgWidth="64" ImgHeight="64" Class="product-thumb" />
|
||||
<MudText>@context.Title</MudText>
|
||||
</MudStack>
|
||||
</MudTd>
|
||||
<MudTd>@FormatPrice(context.UnitPrice)</MudTd>
|
||||
<MudTd>
|
||||
<MudChip T="string" Color="Color.Error" Variant="Variant.Outlined" Size="Size.Small">
|
||||
@context.MaxDiscountPercent% تخفیف
|
||||
</MudChip>
|
||||
</MudTd>
|
||||
<MudTd>
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Remove" Color="Color.Default"
|
||||
OnClick="@(() => DecrementQty(context.ProductId))" />
|
||||
<MudText>@context.Quantity</MudText>
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Add" Color="Color.Default"
|
||||
OnClick="@(() => IncrementQty(context.ProductId))" />
|
||||
</MudStack>
|
||||
</MudTd>
|
||||
<MudTd>
|
||||
@if (context.DiscountAmount > 0)
|
||||
{
|
||||
<MudText Color="Color.Success">@FormatPrice(context.DiscountAmount)-</MudText>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudText Class="mud-text-secondary">—</MudText>
|
||||
}
|
||||
</MudTd>
|
||||
<MudTd>@FormatPrice(context.FinalPrice)</MudTd>
|
||||
<MudTd>
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Delete" Color="Color.Error"
|
||||
OnClick="() => Remove(context.ProductId)" />
|
||||
</MudTd>
|
||||
</RowTemplate>
|
||||
</MudTable>
|
||||
</MudPaper>
|
||||
</MudHidden>
|
||||
|
||||
<!-- Mobile Cards -->
|
||||
<MudHidden Breakpoint="Breakpoint.MdAndUp">
|
||||
<MudStack Spacing="2">
|
||||
@foreach (var item in DiscountCart.Items)
|
||||
{
|
||||
<MudPaper Class="pa-3 rounded-lg w-100-mobile" Outlined="true">
|
||||
<MudStack Spacing="1">
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
||||
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
|
||||
<AppImage Path="@GetImageUrl(item.ImageUrl)" Alt="@item.Title"
|
||||
ImgWidth="50" ImgHeight="50" Class="rounded-circle" />
|
||||
<MudText Typo="Typo.subtitle2">@item.Title</MudText>
|
||||
</MudStack>
|
||||
<MudChip T="string" Color="Color.Error" Variant="Variant.Outlined" Size="Size.Small">
|
||||
@item.MaxDiscountPercent% تخفیف
|
||||
</MudChip>
|
||||
</MudStack>
|
||||
|
||||
<MudStack Spacing="1">
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
||||
<MudText Class="mud-text-secondary">@FormatPrice(item.UnitPrice) واحد</MudText>
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
|
||||
<MudIconButton
|
||||
Icon="@(item.Quantity <= 1 ? Icons.Material.Filled.Delete : Icons.Material.Filled.Remove)"
|
||||
Color="Color.Default"
|
||||
OnClick="@(() => DecrementQty(item.ProductId))" />
|
||||
<MudText>@item.Quantity</MudText>
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Add"
|
||||
Color="Color.Default"
|
||||
OnClick="@(() => IncrementQty(item.ProductId))" />
|
||||
</MudStack>
|
||||
</MudStack>
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
||||
@if (item.DiscountAmount > 0)
|
||||
{
|
||||
<MudText Typo="Typo.caption" Color="Color.Success">تخفیف: @FormatPrice(item.DiscountAmount)-</MudText>
|
||||
}
|
||||
<MudText>جمع: @FormatPrice(item.FinalPrice)</MudText>
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Delete" Color="Color.Error"
|
||||
OnClick="() => Remove(item.ProductId)" />
|
||||
</MudStack>
|
||||
</MudStack>
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
}
|
||||
</MudStack>
|
||||
</MudHidden>
|
||||
|
||||
<!-- Summary -->
|
||||
@if (DeviceDetector.IsMobile())
|
||||
{
|
||||
<MudStack Spacing="1" Class="mobile-actions-stack px-5">
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
||||
<MudText Typo="Typo.body2">جمع کل:</MudText>
|
||||
<MudText Typo="Typo.body2">@FormatPrice(DiscountCart.TotalPrice) تومان</MudText>
|
||||
</MudStack>
|
||||
@if (DiscountCart.TotalDiscount > 0)
|
||||
{
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
||||
<MudText Typo="Typo.caption" Color="Color.Success">سقف تخفیف قابل اعمال:</MudText>
|
||||
<MudText Typo="Typo.caption" Color="Color.Success">@FormatPrice(DiscountCart.TotalDiscount)- تومان</MudText>
|
||||
</MudStack>
|
||||
}
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
||||
<MudText Typo="Typo.h6" Class="fw-bold">حداقل پرداخت:</MudText>
|
||||
<MudText Typo="Typo.h6" Color="Color.Primary" Class="fw-bold">@FormatPrice(DiscountCart.FinalPrice) تومان</MudText>
|
||||
</MudStack>
|
||||
</MudStack>
|
||||
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween" Spacing="2" Class="mobile-actions-stack">
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary"
|
||||
OnClick="() => Navigation.NavigateTo(RouteConstants.DiscountStore.Products)">
|
||||
افزودن محصول
|
||||
</MudButton>
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Success" OnClick="ProceedCheckout"
|
||||
StartIcon="@Icons.Material.Filled.CreditCard">ادامه خرید</MudButton>
|
||||
</MudStack>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudPaper Class="pa-4 rounded-lg" Style="background-color: var(--mud-palette-background-grey);">
|
||||
<MudStack Spacing="1">
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||||
<MudText Typo="Typo.body2">جمع کل:</MudText>
|
||||
<MudText Typo="Typo.body2">@FormatPrice(DiscountCart.TotalPrice) تومان</MudText>
|
||||
</MudStack>
|
||||
@if (DiscountCart.TotalDiscount > 0)
|
||||
{
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||||
<MudText Typo="Typo.body2" Color="Color.Success">سقف تخفیف قابل اعمال:</MudText>
|
||||
<MudText Typo="Typo.body2" Color="Color.Success">@FormatPrice(DiscountCart.TotalDiscount)- تومان</MudText>
|
||||
</MudStack>
|
||||
}
|
||||
<MudDivider Class="my-1" />
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||||
<MudText Typo="Typo.subtitle1" Class="fw-bold">حداقل مبلغ پرداخت درگاه:</MudText>
|
||||
<MudText Typo="Typo.subtitle1" Color="Color.Primary" Class="fw-bold">@FormatPrice(DiscountCart.FinalPrice) تومان</MudText>
|
||||
</MudStack>
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">
|
||||
مبلغ دقیق پرداخت در مرحله تسویه مشخص خواهد شد.
|
||||
</MudText>
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
|
||||
<MudStack Row="true" Justify="Justify.FlexEnd" Spacing="2" Class="mt-3">
|
||||
<MudButton Variant="Variant.Outlined" Color="Color.Primary"
|
||||
OnClick="() => Navigation.NavigateTo(RouteConstants.DiscountStore.Products)">
|
||||
افزودن محصول
|
||||
</MudButton>
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Success" OnClick="ProceedCheckout"
|
||||
StartIcon="@Icons.Material.Filled.CreditCard">ادامه فرایند خرید</MudButton>
|
||||
</MudStack>
|
||||
}
|
||||
}
|
||||
</MudStack>
|
||||
</MudContainer>
|
||||
@@ -0,0 +1,56 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using FrontOffice.Main.Utilities;
|
||||
|
||||
namespace FrontOffice.Main.Pages.DiscountStore;
|
||||
|
||||
public partial class Cart : IDisposable
|
||||
{
|
||||
[Inject] private DiscountCartService DiscountCart { get; set; } = default!;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await DiscountCart.EnsureInitializedAsync();
|
||||
DiscountCart.OnChange += StateHasChanged;
|
||||
}
|
||||
|
||||
private async Task IncrementQty(long productId)
|
||||
{
|
||||
var item = DiscountCart.Items.FirstOrDefault(i => i.ProductId == productId);
|
||||
if (item is null) return;
|
||||
await DiscountCart.UpdateQuantityAsync(productId, item.Quantity + 1);
|
||||
}
|
||||
|
||||
private async Task DecrementQty(long productId)
|
||||
{
|
||||
var item = DiscountCart.Items.FirstOrDefault(i => i.ProductId == productId);
|
||||
if (item is null) return;
|
||||
if (item.Quantity <= 1)
|
||||
{
|
||||
await DiscountCart.RemoveAsync(productId);
|
||||
}
|
||||
else
|
||||
{
|
||||
await DiscountCart.UpdateQuantityAsync(productId, item.Quantity - 1);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Remove(long productId)
|
||||
{
|
||||
await DiscountCart.RemoveAsync(productId);
|
||||
}
|
||||
|
||||
private void ProceedCheckout()
|
||||
{
|
||||
Navigation.NavigateTo(RouteConstants.DiscountStore.Checkout);
|
||||
}
|
||||
|
||||
private static string FormatPrice(long price) => $"{price:N0} ";
|
||||
|
||||
private static string GetImageUrl(string? imageUrl)
|
||||
=> string.IsNullOrWhiteSpace(imageUrl) ? "/images/product-placeholder.svg" : imageUrl.TrimStart('/');
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
DiscountCart.OnChange -= StateHasChanged;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
@using CMSMicroservice.Protobuf.Protos.UserAddress
|
||||
@attribute [Route(RouteConstants.DiscountStore.Checkout)]
|
||||
|
||||
<PageTitle>تسویه حساب فروشگاه تخفیفی</PageTitle>
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
||||
<PageHeader Title="تسویه حساب تخفیفی" BackHref="@RouteConstants.DiscountStore.Cart" />
|
||||
<MudGrid Spacing="3">
|
||||
<!-- Left Column: Address + Payment Settings -->
|
||||
<MudItem xs="12" md="8">
|
||||
<!-- Address Selection -->
|
||||
<MudPaper Elevation="2" Class="pa-4 rounded-lg mb-3">
|
||||
<MudText Typo="Typo.h6" Class="mb-2">انتخاب آدرس</MudText>
|
||||
|
||||
@if (_loadingAddresses)
|
||||
{
|
||||
<MudStack AlignItems="AlignItems.Center" Class="py-4">
|
||||
<MudProgressCircular Indeterminate="true" Color="Color.Primary" />
|
||||
<MudText Class="mt-2 mud-text-secondary">در حال بارگذاری آدرسها...</MudText>
|
||||
</MudStack>
|
||||
}
|
||||
else if (_addresses.Count == 0)
|
||||
{
|
||||
<MudAlert Severity="Severity.Warning">
|
||||
هیچ آدرسی ثبت نشده است. لطفاً از بخش پروفایل آدرس خود را اضافه کنید.
|
||||
</MudAlert>
|
||||
<MudButton Class="mt-2" Variant="Variant.Outlined" Href="/profile/addresses">افزودن آدرس</MudButton>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudStack Spacing="1">
|
||||
@foreach (var address in _addresses)
|
||||
{
|
||||
<MudPaper Outlined="@(_selectedAddress?.Id != address.Id)"
|
||||
Elevation="@(_selectedAddress?.Id == address.Id ? 2 : 0)"
|
||||
Class="pa-3 rounded-xl cursor-pointer"
|
||||
Style="@(_selectedAddress?.Id == address.Id ? "border: 2px solid var(--mud-palette-primary);" : "")"
|
||||
@onclick="() => _selectedAddress = address">
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
||||
<MudStack>
|
||||
<MudText Typo="Typo.subtitle2">@address.Title</MudText>
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary">@address.Address</MudText>
|
||||
</MudStack>
|
||||
@if (address.IsDefault)
|
||||
{
|
||||
<MudChip T="string" Color="Color.Success" Variant="Variant.Outlined">پیشفرض</MudChip>
|
||||
}
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
}
|
||||
</MudStack>
|
||||
}
|
||||
</MudPaper>
|
||||
|
||||
<!-- Notes -->
|
||||
<MudPaper Elevation="2" Class="pa-4 rounded-lg">
|
||||
<MudText Typo="Typo.h6" Class="mb-2">توضیحات سفارش</MudText>
|
||||
<MudTextField T="string" @bind-Value="_notes" Lines="3"
|
||||
Placeholder="توضیحات اختیاری..."
|
||||
Variant="Variant.Outlined" />
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
|
||||
<!-- Right Column: Order Summary -->
|
||||
<MudItem xs="12" md="4">
|
||||
<MudPaper Elevation="2" Class="pa-4 rounded-lg" Style="position:sticky; top:80px;">
|
||||
<MudText Typo="Typo.h6" Class="mb-2">خلاصه سفارش</MudText>
|
||||
|
||||
@if (DiscountCart.Items.Count == 0)
|
||||
{
|
||||
<MudAlert Severity="Severity.Info">سبد خرید شما خالی است.</MudAlert>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudList T="string" Dense="true">
|
||||
@foreach (var item in DiscountCart.Items)
|
||||
{
|
||||
<MudListItem T="string">
|
||||
<MudListItemText>
|
||||
<MudStack Spacing="1">
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
||||
<AppImage Path="@GetImageUrl(item.ImageUrl)" Alt="@item.Title"
|
||||
ImgWidth="40" ImgHeight="40" Class="rounded-circle" />
|
||||
<MudText Typo="Typo.subtitle2" Style="flex:1; margin:0 8px;">@item.Title</MudText>
|
||||
<MudText Typo="Typo.subtitle2">@FormatPrice(item.FinalPrice)</MudText>
|
||||
</MudStack>
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">
|
||||
@item.Quantity × @FormatPrice(item.UnitPrice)
|
||||
</MudText>
|
||||
@if (item.MaxDiscountPercent > 0)
|
||||
{
|
||||
<MudText Typo="Typo.caption" Color="Color.Error">@item.MaxDiscountPercent% تخفیف</MudText>
|
||||
}
|
||||
</MudStack>
|
||||
</MudStack>
|
||||
</MudListItemText>
|
||||
</MudListItem>
|
||||
}
|
||||
</MudList>
|
||||
|
||||
<MudDivider Class="my-2" />
|
||||
|
||||
<MudStack Spacing="1">
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||||
<MudText Typo="Typo.body2">جمع کل:</MudText>
|
||||
<MudText Typo="Typo.body2">@FormatPrice(DiscountCart.TotalPrice) تومان</MudText>
|
||||
</MudStack>
|
||||
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||||
<MudText Typo="Typo.body2" Color="Color.Success">تخفیف از کیف تخفیفی:</MudText>
|
||||
<MudText Typo="Typo.body2" Color="Color.Success">@FormatPrice(DiscountCart.TotalDiscount)- تومان</MudText>
|
||||
</MudStack>
|
||||
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||||
<MudText Typo="Typo.body2">مبلغ پس از تخفیف:</MudText>
|
||||
<MudText Typo="Typo.body2">@FormatPrice(NetGatewayAmount) تومان</MudText>
|
||||
</MudStack>
|
||||
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary">مالیات بر ارزش افزوده (۹٪):</MudText>
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary">@FormatPrice(VatAmount)+ تومان</MudText>
|
||||
</MudStack>
|
||||
|
||||
<MudDivider Class="my-1" />
|
||||
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||||
<MudText Typo="Typo.subtitle1" Class="fw-bold">مبلغ قابل پرداخت:</MudText>
|
||||
<MudText Typo="Typo.subtitle1" Color="Color.Primary" Class="fw-bold">
|
||||
@FormatPrice(FinalGatewayAmount) تومان
|
||||
</MudText>
|
||||
</MudStack>
|
||||
</MudStack>
|
||||
|
||||
<MudButton Disabled="@(!CanPlaceOrder || _placing)" Class="mt-3 w-100-mobile"
|
||||
Variant="Variant.Filled" Color="Color.Primary"
|
||||
OnClick="PlaceOrder" FullWidth="true"
|
||||
StartIcon="@Icons.Material.Filled.CheckCircle">
|
||||
@(_placing ? "در حال ثبت سفارش..." : "ثبت و پرداخت سفارش")
|
||||
</MudButton>
|
||||
}
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudContainer>
|
||||
@@ -0,0 +1,119 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using CMSMicroservice.Protobuf.Protos.UserAddress;
|
||||
using FrontOffice.Main.Utilities;
|
||||
using MudBlazor;
|
||||
|
||||
namespace FrontOffice.Main.Pages.DiscountStore;
|
||||
|
||||
public partial class Checkout
|
||||
{
|
||||
[Inject] private DiscountCartService DiscountCart { get; set; } = default!;
|
||||
[Inject] private DiscountOrderService DiscountOrderService { get; set; } = default!;
|
||||
[Inject] private UserAddressContract.UserAddressContractClient UserAddressContract { get; set; } = default!;
|
||||
|
||||
private List<CustomerAddressModel> _addresses = new();
|
||||
private CustomerAddressModel? _selectedAddress;
|
||||
private bool _loadingAddresses;
|
||||
|
||||
private string? _notes;
|
||||
private bool _placing;
|
||||
|
||||
private const decimal VAT_RATE = 0.09m;
|
||||
|
||||
/// <summary>مبلغ درگاه قبل از مالیات (جمع کل - تخفیف)</summary>
|
||||
private long NetGatewayAmount => DiscountCart.TotalPrice - DiscountCart.TotalDiscount;
|
||||
|
||||
/// <summary>مالیات بر ارزش افزوده ۹٪</summary>
|
||||
private long VatAmount => (long)(NetGatewayAmount * VAT_RATE);
|
||||
|
||||
/// <summary>مبلغ نهایی قابل پرداخت (شامل VAT)</summary>
|
||||
private long FinalGatewayAmount => NetGatewayAmount + VatAmount;
|
||||
|
||||
private bool CanPlaceOrder => DiscountCart.Items.Count > 0 && _selectedAddress is not null;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await DiscountCart.EnsureInitializedAsync();
|
||||
await LoadAddresses();
|
||||
}
|
||||
|
||||
private async Task LoadAddresses()
|
||||
{
|
||||
_loadingAddresses = true;
|
||||
try
|
||||
{
|
||||
var response = await UserAddressContract.GetCustomerAddressesAsync(new());
|
||||
if (response?.Models?.Any() == true)
|
||||
{
|
||||
_addresses = response.Models.ToList();
|
||||
_selectedAddress = _addresses.FirstOrDefault(a => a.IsDefault) ?? _addresses.First();
|
||||
}
|
||||
else
|
||||
{
|
||||
_addresses = new();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"خطا در بارگذاری آدرسها: {ex.Message}", Severity.Error);
|
||||
_addresses = new();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_loadingAddresses = false;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task PlaceOrder()
|
||||
{
|
||||
if (!CanPlaceOrder || _selectedAddress is null)
|
||||
{
|
||||
Snackbar.Add("لطفاً آدرس را انتخاب کنید.", Severity.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
_placing = true;
|
||||
try
|
||||
{
|
||||
// Always use 100% discount — send full price, server will apply max from wallet
|
||||
var result = await DiscountOrderService.PlaceOrderAsync(
|
||||
_selectedAddress.Id,
|
||||
DiscountCart.TotalPrice,
|
||||
_notes);
|
||||
|
||||
if (!result.Success)
|
||||
{
|
||||
Snackbar.Add(result.Message, Severity.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
// If there's a gateway payment URL, redirect to it
|
||||
if (!string.IsNullOrWhiteSpace(result.PaymentUrl) && result.GatewayAmount > 0)
|
||||
{
|
||||
Snackbar.Add("در حال انتقال به درگاه پرداخت...", Severity.Info);
|
||||
await DiscountCart.ClearAsync();
|
||||
Navigation.NavigateTo(result.PaymentUrl, forceLoad: true);
|
||||
return;
|
||||
}
|
||||
|
||||
// If fully paid via discount balance (no gateway needed)
|
||||
await DiscountCart.ClearAsync();
|
||||
Snackbar.Add("سفارش با موفقیت ثبت شد!", Severity.Success);
|
||||
Navigation.NavigateTo($"{RouteConstants.DiscountStore.OrderDetail}{result.OrderId}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"خطا در ثبت سفارش: {ex.Message}", Severity.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_placing = false;
|
||||
}
|
||||
}
|
||||
|
||||
private static string FormatPrice(long price) => $"{price:N0}";
|
||||
|
||||
private static string GetImageUrl(string? imageUrl)
|
||||
=> string.IsNullOrWhiteSpace(imageUrl) ? "/images/product-placeholder.svg" : imageUrl.TrimStart('/');
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
@attribute [Route("/discount-store/order/{Id:long}")]
|
||||
|
||||
<PageTitle>جزئیات سفارش تخفیفی</PageTitle>
|
||||
|
||||
@if (!string.IsNullOrEmpty(_paymentMessage))
|
||||
{
|
||||
<MudContainer MaxWidth="MaxWidth.Medium" Class="pt-4">
|
||||
<MudAlert Severity="@_paymentSeverity" Variant="Variant.Filled" Class="mb-2"
|
||||
CloseIconClicked="() => _paymentMessage = null" ShowCloseIcon="true">
|
||||
@_paymentMessage
|
||||
</MudAlert>
|
||||
</MudContainer>
|
||||
}
|
||||
|
||||
@if (_loading)
|
||||
{
|
||||
<MudContainer MaxWidth="MaxWidth.Medium" Class="py-6">
|
||||
<LoadingState />
|
||||
</MudContainer>
|
||||
}
|
||||
else if (_order is null)
|
||||
{
|
||||
<MudContainer MaxWidth="MaxWidth.Medium" Class="py-6">
|
||||
<EmptyState Icon="@Icons.Material.Filled.SearchOff"
|
||||
Title="سفارش مورد نظر یافت نشد."
|
||||
ActionText="بازگشت به لیست سفارشها"
|
||||
ActionHref="@RouteConstants.DiscountStore.Orders" />
|
||||
</MudContainer>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
||||
<!-- Header -->
|
||||
<PageHeader Title="@($"سفارش #{_order.OrderNumber}")" BackHref="@RouteConstants.DiscountStore.Orders" />
|
||||
<MudStack Row="true" Spacing="2" Class="flex-wrap mb-4">
|
||||
<MudChip T="string" Color="@DiscountOrderService.GetPaymentStatusColor(_order.PaymentStatus)"
|
||||
Variant="Variant.Filled">
|
||||
@DiscountOrderService.GetPaymentStatusText(_order.PaymentStatus)
|
||||
</MudChip>
|
||||
<MudChip T="string" Color="@DiscountOrderService.GetDeliveryStatusColor(_order.DeliveryStatus)"
|
||||
Variant="Variant.Outlined">
|
||||
@DiscountOrderService.GetDeliveryStatusText(_order.DeliveryStatus)
|
||||
</MudChip>
|
||||
</MudStack>
|
||||
|
||||
@if (_order.PaymentStatus == 2)
|
||||
{
|
||||
<MudAlert Severity="Severity.Error" Variant="Variant.Outlined" Class="mb-4">
|
||||
<MudText>پرداخت این سفارش ناموفق بود و سفارش منقضی شده است. موجودی رزرو شده آزاد شد.</MudText>
|
||||
<MudButton Variant="Variant.Text" Color="Color.Primary" Class="mt-2"
|
||||
Href="@RouteConstants.DiscountStore.Products">
|
||||
بازگشت به فروشگاه و ثبت سفارش جدید
|
||||
</MudButton>
|
||||
</MudAlert>
|
||||
}
|
||||
|
||||
<MudPaper Elevation="2" Class="pa-4 rounded-lg">
|
||||
<MudStack Spacing="2">
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">
|
||||
تاریخ ثبت: @FormatDate(_order.Created)
|
||||
</MudText>
|
||||
|
||||
@* ── Address ── *@
|
||||
@if (_order.Address is not null)
|
||||
{
|
||||
<MudText Typo="Typo.body2">
|
||||
<b>آدرس:</b> @_order.Address.Title — @_order.Address.Address
|
||||
</MudText>
|
||||
@if (!string.IsNullOrWhiteSpace(_order.Address.PostalCode))
|
||||
{
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">کد پستی: @_order.Address.PostalCode</MudText>
|
||||
}
|
||||
}
|
||||
|
||||
<MudDivider Class="my-2" />
|
||||
|
||||
@* ── Desktop: Table ── *@
|
||||
<MudHidden Breakpoint="Breakpoint.MdAndUp" Invert="true">
|
||||
<MudText Typo="Typo.h6" Class="mb-2">اقلام سفارش</MudText>
|
||||
<MudTable Items="_order.Items" Dense="true">
|
||||
<HeaderContent>
|
||||
<MudTh>محصول</MudTh>
|
||||
<MudTh>قیمت واحد</MudTh>
|
||||
<MudTh>تعداد</MudTh>
|
||||
<MudTh>سقف تخفیف</MudTh>
|
||||
<MudTh>تخفیف</MudTh>
|
||||
<MudTh>قیمت نهایی</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd>@context.Title</MudTd>
|
||||
<MudTd>@FormatPrice(context.UnitPrice)</MudTd>
|
||||
<MudTd>@context.Count</MudTd>
|
||||
<MudTd>
|
||||
@if (context.MaxDiscountPercent > 0)
|
||||
{
|
||||
<MudChip T="string" Size="Size.Small" Color="Color.Error" Variant="Variant.Outlined">@context.MaxDiscountPercent%</MudChip>
|
||||
}
|
||||
</MudTd>
|
||||
<MudTd>
|
||||
@if (context.DiscountAmount > 0)
|
||||
{
|
||||
<MudText Color="Color.Success">@FormatPrice(context.DiscountAmount)-</MudText>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudText Class="mud-text-secondary">—</MudText>
|
||||
}
|
||||
</MudTd>
|
||||
<MudTd>@FormatPrice(context.FinalPrice)</MudTd>
|
||||
</RowTemplate>
|
||||
</MudTable>
|
||||
</MudHidden>
|
||||
|
||||
@* ── Mobile: Cards ── *@
|
||||
<MudHidden Breakpoint="Breakpoint.MdAndUp">
|
||||
<MudText Typo="Typo.h6" Class="mb-2">اقلام سفارش</MudText>
|
||||
<MudStack Spacing="2">
|
||||
@foreach (var item in _order.Items)
|
||||
{
|
||||
<MudPaper Class="pa-3 rounded-lg" Outlined="true">
|
||||
<MudStack Spacing="1">
|
||||
<MudText Typo="Typo.subtitle2">@item.Title</MudText>
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary">قیمت واحد:</MudText>
|
||||
<MudText Typo="Typo.body2">@FormatPrice(item.UnitPrice)</MudText>
|
||||
</MudStack>
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary">تعداد:</MudText>
|
||||
<MudText Typo="Typo.body2">@item.Count</MudText>
|
||||
</MudStack>
|
||||
@if (item.DiscountAmount > 0)
|
||||
{
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||||
<MudText Typo="Typo.body2" Color="Color.Success">تخفیف (@item.MaxDiscountPercent%):</MudText>
|
||||
<MudText Typo="Typo.body2" Color="Color.Success">@FormatPrice(item.DiscountAmount)-</MudText>
|
||||
</MudStack>
|
||||
}
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||||
<MudText Typo="Typo.subtitle2">قیمت نهایی:</MudText>
|
||||
<MudText Typo="Typo.subtitle2" Color="Color.Primary">@FormatPrice(item.FinalPrice)</MudText>
|
||||
</MudStack>
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
}
|
||||
</MudStack>
|
||||
</MudHidden>
|
||||
|
||||
<MudDivider Class="my-2" />
|
||||
|
||||
@* ── Financial Summary ── *@
|
||||
<MudStack Spacing="1" Class="pa-3" Style="background-color: var(--mud-palette-background-grey); border-radius: 8px;">
|
||||
<MudText Typo="Typo.h6" Class="mb-1">خلاصه مالی</MudText>
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||||
<MudText Typo="Typo.body2">جمع کل:</MudText>
|
||||
<MudText Typo="Typo.body2">@FormatPrice(_order.TotalPrice)</MudText>
|
||||
</MudStack>
|
||||
|
||||
@if (_order.DiscountBalanceUsed > 0)
|
||||
{
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||||
<MudText Typo="Typo.body2" Color="Color.Success">از کیف تخفیفی:</MudText>
|
||||
<MudText Typo="Typo.body2" Color="Color.Success">@FormatPrice(_order.DiscountBalanceUsed)-</MudText>
|
||||
</MudStack>
|
||||
}
|
||||
|
||||
<MudDivider Class="my-1" />
|
||||
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||||
<MudText Typo="Typo.subtitle1" Class="fw-bold">پرداخت درگاه:</MudText>
|
||||
<MudText Typo="Typo.subtitle1" Color="Color.Primary" Class="fw-bold">
|
||||
@FormatPrice(_order.GatewayAmount)
|
||||
</MudText>
|
||||
</MudStack>
|
||||
</MudStack>
|
||||
|
||||
@* ── Tracking / Transaction ── *@
|
||||
@if (!string.IsNullOrWhiteSpace(_order.TrackingCode))
|
||||
{
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||||
<MudText Typo="Typo.body2">کد رهگیری:</MudText>
|
||||
<MudText Typo="Typo.body2" Class="fw-semibold">@_order.TrackingCode</MudText>
|
||||
</MudStack>
|
||||
}
|
||||
@if (!string.IsNullOrWhiteSpace(_order.TransactionId))
|
||||
{
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">شناسه تراکنش:</MudText>
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">@_order.TransactionId</MudText>
|
||||
</MudStack>
|
||||
}
|
||||
|
||||
@* ── Notes ── *@
|
||||
@if (!string.IsNullOrWhiteSpace(_order.Notes))
|
||||
{
|
||||
<MudDivider Class="my-1" />
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary">
|
||||
<b>توضیحات:</b> @_order.Notes
|
||||
</MudText>
|
||||
}
|
||||
@if (!string.IsNullOrWhiteSpace(_order.AdminNotes))
|
||||
{
|
||||
<MudAlert Severity="Severity.Info" Variant="Variant.Outlined" Dense="true">
|
||||
<b>پیام مدیر:</b> @_order.AdminNotes
|
||||
</MudAlert>
|
||||
}
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
</MudContainer>
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using DateTimeConverterCL;
|
||||
using FrontOffice.Main.Utilities;
|
||||
|
||||
namespace FrontOffice.Main.Pages.DiscountStore;
|
||||
|
||||
public partial class OrderDetail
|
||||
{
|
||||
[Parameter] public long Id { get; set; }
|
||||
|
||||
[SupplyParameterFromQuery(Name = "payment")]
|
||||
public string? PaymentResult { get; set; }
|
||||
|
||||
[Inject] private DiscountOrderService DiscountOrderService { get; set; } = default!;
|
||||
|
||||
private DiscountOrderDetail? _order;
|
||||
private bool _loading = true;
|
||||
private string? _paymentMessage;
|
||||
private MudBlazor.Severity _paymentSeverity;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
// نمایش پیام نتیجه پرداخت (بعد از بازگشت از درگاه)
|
||||
if (!string.IsNullOrEmpty(PaymentResult))
|
||||
{
|
||||
switch (PaymentResult.ToLower())
|
||||
{
|
||||
case "success":
|
||||
_paymentMessage = "پرداخت با موفقیت انجام شد! سفارش شما ثبت نهایی شد.";
|
||||
_paymentSeverity = MudBlazor.Severity.Success;
|
||||
break;
|
||||
case "failed":
|
||||
_paymentMessage = "پرداخت ناموفق بود. مبلغ رزرو شده آزاد خواهد شد.";
|
||||
_paymentSeverity = MudBlazor.Severity.Error;
|
||||
break;
|
||||
case "error":
|
||||
_paymentMessage = "خطایی در پردازش پرداخت رخ داد. لطفاً با پشتیبانی تماس بگیرید.";
|
||||
_paymentSeverity = MudBlazor.Severity.Warning;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
await LoadOrderAsync();
|
||||
}
|
||||
|
||||
private async Task LoadOrderAsync()
|
||||
{
|
||||
_loading = true;
|
||||
try
|
||||
{
|
||||
_order = await DiscountOrderService.GetOrderByIdAsync(Id);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Snackbar.Add("خطا در بارگذاری سفارش", MudBlazor.Severity.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private static string FormatPrice(long price) => $"{price:N0} تومان";
|
||||
|
||||
private static string FormatDate(DateTime date)
|
||||
{
|
||||
try
|
||||
{
|
||||
return date.ToLocalTime().MiladiToJalaliWithTime();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return date.ToString("yyyy/MM/dd");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
@attribute [Route(RouteConstants.DiscountStore.Orders)]
|
||||
|
||||
<PageTitle>سفارشهای فروشگاه تخفیفی</PageTitle>
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
||||
<MudStack Spacing="3">
|
||||
<PageHeader Title="سفارشهای تخفیفی" BackHref="@RouteConstants.DiscountStore.Products" />
|
||||
|
||||
@if (_loading)
|
||||
{
|
||||
<LoadingState />
|
||||
}
|
||||
else if (_orders.Count == 0)
|
||||
{
|
||||
<MudAlert Severity="Severity.Info">هنوز سفارشی ثبت نشده است.</MudAlert>
|
||||
<MudButton Variant="Variant.Outlined" StartIcon="@Icons.Material.Filled.Store"
|
||||
Href="@RouteConstants.DiscountStore.Products">
|
||||
مشاهده فروشگاه تخفیفی
|
||||
</MudButton>
|
||||
}
|
||||
else
|
||||
{
|
||||
<!-- Desktop Table -->
|
||||
<MudHidden Breakpoint="Breakpoint.MdAndUp" Invert="true">
|
||||
<MudPaper Elevation="1" Class="pa-4 rounded-lg">
|
||||
<MudTable Items="_orders">
|
||||
<HeaderContent>
|
||||
<MudTh>شماره سفارش</MudTh>
|
||||
<MudTh>تعداد اقلام</MudTh>
|
||||
<MudTh>مبلغ کل</MudTh>
|
||||
<MudTh>از کیف تخفیفی</MudTh>
|
||||
<MudTh>درگاه</MudTh>
|
||||
<MudTh>وضعیت پرداخت</MudTh>
|
||||
<MudTh>وضعیت ارسال</MudTh>
|
||||
<MudTh>تاریخ</MudTh>
|
||||
<MudTh></MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd>@context.OrderNumber</MudTd>
|
||||
<MudTd>@context.ItemsCount</MudTd>
|
||||
<MudTd>@FormatPrice(context.TotalPrice)</MudTd>
|
||||
<MudTd>
|
||||
@if (context.DiscountBalanceUsed > 0)
|
||||
{
|
||||
<MudText Color="Color.Success">@FormatPrice(context.DiscountBalanceUsed)</MudText>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudText Class="mud-text-secondary">—</MudText>
|
||||
}
|
||||
</MudTd>
|
||||
<MudTd>@FormatPrice(context.GatewayAmount)</MudTd>
|
||||
<MudTd>
|
||||
<MudChip T="string" Size="Size.Small"
|
||||
Color="@DiscountOrderService.GetPaymentStatusColor(context.PaymentStatus)"
|
||||
Variant="Variant.Filled">
|
||||
@DiscountOrderService.GetPaymentStatusText(context.PaymentStatus)
|
||||
</MudChip>
|
||||
</MudTd>
|
||||
<MudTd>
|
||||
<MudChip T="string" Size="Size.Small"
|
||||
Color="@DiscountOrderService.GetDeliveryStatusColor(context.DeliveryStatus)"
|
||||
Variant="Variant.Outlined">
|
||||
@DiscountOrderService.GetDeliveryStatusText(context.DeliveryStatus)
|
||||
</MudChip>
|
||||
</MudTd>
|
||||
<MudTd>@FormatDate(context.Created)</MudTd>
|
||||
<MudTd>
|
||||
<MudButton Size="Size.Small" Variant="Variant.Outlined" Color="Color.Primary"
|
||||
OnClick="() => ViewOrder(context.Id)">
|
||||
جزئیات
|
||||
</MudButton>
|
||||
</MudTd>
|
||||
</RowTemplate>
|
||||
</MudTable>
|
||||
</MudPaper>
|
||||
</MudHidden>
|
||||
|
||||
<!-- Mobile Cards -->
|
||||
<MudHidden Breakpoint="Breakpoint.MdAndUp">
|
||||
<MudStack Spacing="2">
|
||||
@foreach (var order in _orders)
|
||||
{
|
||||
<MudPaper Class="pa-3 rounded-lg cursor-pointer" Outlined="true"
|
||||
@onclick="() => ViewOrder(order.Id)">
|
||||
<MudStack Spacing="1">
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
||||
<MudText Typo="Typo.subtitle2">سفارش #@order.OrderNumber</MudText>
|
||||
<MudChip T="string" Size="Size.Small"
|
||||
Color="@DiscountOrderService.GetPaymentStatusColor(order.PaymentStatus)"
|
||||
Variant="Variant.Filled">
|
||||
@DiscountOrderService.GetPaymentStatusText(order.PaymentStatus)
|
||||
</MudChip>
|
||||
</MudStack>
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">@FormatDate(order.Created)</MudText>
|
||||
<MudChip T="string" Size="Size.Small"
|
||||
Color="@DiscountOrderService.GetDeliveryStatusColor(order.DeliveryStatus)"
|
||||
Variant="Variant.Outlined">
|
||||
@DiscountOrderService.GetDeliveryStatusText(order.DeliveryStatus)
|
||||
</MudChip>
|
||||
</MudStack>
|
||||
<MudDivider />
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||||
<MudText Typo="Typo.body2">@order.ItemsCount قلم</MudText>
|
||||
<MudText Typo="Typo.body2" Class="fw-semibold">@FormatPrice(order.TotalPrice) تومان</MudText>
|
||||
</MudStack>
|
||||
@if (order.DiscountBalanceUsed > 0)
|
||||
{
|
||||
<MudText Typo="Typo.caption" Color="Color.Success">
|
||||
از کیف تخفیفی: @FormatPrice(order.DiscountBalanceUsed) تومان
|
||||
</MudText>
|
||||
}
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
}
|
||||
</MudStack>
|
||||
</MudHidden>
|
||||
|
||||
@if (_totalPages > 1)
|
||||
{
|
||||
<MudStack AlignItems="AlignItems.Center" Class="mt-4">
|
||||
<MudPagination Count="@_totalPages" Selected="@_currentPage"
|
||||
SelectedChanged="OnPageChanged" Color="Color.Primary"
|
||||
Variant="Variant.Outlined" />
|
||||
</MudStack>
|
||||
}
|
||||
}
|
||||
</MudStack>
|
||||
</MudContainer>
|
||||
@@ -0,0 +1,65 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using DateTimeConverterCL;
|
||||
using FrontOffice.Main.Utilities;
|
||||
|
||||
namespace FrontOffice.Main.Pages.DiscountStore;
|
||||
|
||||
public partial class Orders
|
||||
{
|
||||
[Inject] private DiscountOrderService DiscountOrderService { get; set; } = default!;
|
||||
|
||||
private List<DiscountOrderSummary> _orders = new();
|
||||
private int _currentPage = 1;
|
||||
private int _totalPages;
|
||||
private bool _loading = true;
|
||||
private const int PageSize = 10;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await LoadOrdersAsync();
|
||||
}
|
||||
|
||||
private async Task LoadOrdersAsync()
|
||||
{
|
||||
_loading = true;
|
||||
try
|
||||
{
|
||||
var result = await DiscountOrderService.GetUserOrdersAsync(_currentPage, PageSize);
|
||||
_orders = result.Orders;
|
||||
_totalPages = result.TotalPages;
|
||||
}
|
||||
catch
|
||||
{
|
||||
Snackbar.Add("خطا در بارگذاری سفارشها", MudBlazor.Severity.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OnPageChanged(int page)
|
||||
{
|
||||
_currentPage = page;
|
||||
await LoadOrdersAsync();
|
||||
}
|
||||
|
||||
private void ViewOrder(long orderId)
|
||||
{
|
||||
Navigation.NavigateTo($"{RouteConstants.DiscountStore.OrderDetail}{orderId}");
|
||||
}
|
||||
|
||||
private static string FormatPrice(long price) => $"{price:N0}";
|
||||
|
||||
private static string FormatDate(DateTime date)
|
||||
{
|
||||
try
|
||||
{
|
||||
return date.ToLocalTime().MiladiToJalaliWithTime();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return date.ToString("yyyy/MM/dd");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
@attribute [Route("/discount-store/product/{Id:long}")]
|
||||
|
||||
<PageTitle>@(_product?.Title ?? "محصول تخفیفی")</PageTitle>
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.Large" Class="py-4 py-md-6">
|
||||
@if (_loading)
|
||||
{
|
||||
<LoadingState />
|
||||
}
|
||||
else if (_product is null)
|
||||
{
|
||||
<EmptyState Icon="@Icons.Material.Filled.SearchOff"
|
||||
Title="محصول مورد نظر یافت نشد."
|
||||
ActionText="بازگشت به فروشگاه تخفیفی"
|
||||
ActionHref="@RouteConstants.DiscountStore.Products" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<!-- Breadcrumb -->
|
||||
<PageHeader Title="جزئیات محصول تخفیفی" BackHref="@RouteConstants.DiscountStore.Products" />
|
||||
|
||||
<MudGrid Spacing="4">
|
||||
<!-- Image Gallery -->
|
||||
<MudItem xs="12" md="5">
|
||||
<MudPaper Class="pa-2 rounded-xl" Elevation="1">
|
||||
<AppImage Path="@_selectedImage" Alt="@_product.Title"
|
||||
ObjectFit="ObjectFit.Contain"
|
||||
Style="width:100%; max-height:400px; border-radius:12px;" />
|
||||
|
||||
@if (_product.Images.Count > 1)
|
||||
{
|
||||
<MudStack Row="true" Spacing="1" Class="mt-2 flex-wrap" Justify="Justify.Center">
|
||||
@foreach (var img in _product.Images)
|
||||
{
|
||||
<span @onclick="() => _selectedImage = img.ImageUrl" style="cursor:pointer">
|
||||
<AppImage Path="@img.ThumbnailUrl" Alt="@(img.Title ?? "")"
|
||||
ImgWidth="60" ImgHeight="60"
|
||||
ObjectFit="ObjectFit.Cover"
|
||||
Class="rounded-lg"
|
||||
Style="@($"border: 2px solid {(img.ImageUrl == _selectedImage ? "var(--mud-palette-primary)" : "transparent")};")"
|
||||
/>
|
||||
</span>
|
||||
}
|
||||
</MudStack>
|
||||
}
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
|
||||
<!-- Product Info -->
|
||||
<MudItem xs="12" md="7">
|
||||
<MudStack Spacing="3">
|
||||
<MudText Typo="Typo.h5" Class="fw-bold">@_product.Title</MudText>
|
||||
|
||||
<!-- Categories -->
|
||||
@if (_product.Categories.Any())
|
||||
{
|
||||
<MudStack Row="true" Spacing="1" Class="flex-wrap">
|
||||
@foreach (var cat in _product.Categories)
|
||||
{
|
||||
<MudChip T="string" Color="Color.Info" Variant="Variant.Outlined" Size="Size.Small">@cat.Title</MudChip>
|
||||
}
|
||||
</MudStack>
|
||||
}
|
||||
|
||||
<!-- Price & Discount -->
|
||||
<MudPaper Class="pa-4 rounded-lg discount-price-box" Elevation="0">
|
||||
<MudStack Spacing="2">
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary">قیمت محصول:</MudText>
|
||||
<MudText Typo="Typo.h5" Color="Color.Primary" Class="fw-bold">
|
||||
@($"{_product.Price:N0}") تومان
|
||||
</MudText>
|
||||
</MudStack>
|
||||
@if (_product.MaxDiscountPercent > 0)
|
||||
{
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary">تخفیف از کیف تخفیفی:</MudText>
|
||||
<MudChip T="string" Color="Color.Error" Variant="Variant.Filled" Size="Size.Small">
|
||||
@_product.MaxDiscountPercent% (@($"{_product.Price * _product.MaxDiscountPercent / 100:N0}") تومان)
|
||||
</MudChip>
|
||||
</MudStack>
|
||||
}
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
|
||||
<!-- Stock & Stats -->
|
||||
<MudStack Row="true" Spacing="3" Class="flex-wrap">
|
||||
<MudChip T="string" Variant="Variant.Outlined" Size="Size.Small"
|
||||
Color="@(_product.RemainingCount > 0 ? Color.Success : Color.Error)"
|
||||
Icon="@Icons.Material.Filled.Inventory">
|
||||
@(_product.RemainingCount > 0 ? $"موجود ({_product.RemainingCount} عدد)" : "ناموجود")
|
||||
</MudChip>
|
||||
<MudChip T="string" Variant="Variant.Outlined" Size="Size.Small" Color="Color.Default"
|
||||
Icon="@Icons.Material.Filled.Visibility">
|
||||
@_product.ViewCount بازدید
|
||||
</MudChip>
|
||||
</MudStack>
|
||||
|
||||
<!-- Short Info -->
|
||||
@if (!string.IsNullOrWhiteSpace(_product.ShortInfo))
|
||||
{
|
||||
<MudText Typo="Typo.body1" Class="mud-text-secondary">@_product.ShortInfo</MudText>
|
||||
}
|
||||
|
||||
<!-- Add to Cart -->
|
||||
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center" Class="flex-wrap">
|
||||
<MudNumericField T="int" @bind-Value="_quantity" Min="1"
|
||||
Max="@(_product.RemainingCount > 0 ? _product.RemainingCount : 1)"
|
||||
Label="تعداد" Variant="Variant.Outlined" Margin="Margin.Dense"
|
||||
Style="width:100px;" />
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Success"
|
||||
StartIcon="@Icons.Material.Filled.AddShoppingCart"
|
||||
OnClick="AddToCart"
|
||||
Disabled="@(_product.RemainingCount <= 0 || _addingToCart)"
|
||||
Size="Size.Large">
|
||||
@(_addingToCart ? "در حال افزودن..." : "افزودن به سبد")
|
||||
</MudButton>
|
||||
<MudButton Variant="Variant.Outlined" Color="Color.Primary"
|
||||
StartIcon="@Icons.Material.Filled.ShoppingCart"
|
||||
Href="@RouteConstants.DiscountStore.Cart">
|
||||
مشاهده سبد
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
</MudStack>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
|
||||
<!-- Full Information -->
|
||||
@if (!string.IsNullOrWhiteSpace(_product.FullInfo))
|
||||
{
|
||||
<MudPaper Elevation="1" Class="pa-4 pa-md-6 mt-6 rounded-lg">
|
||||
<MudText Typo="Typo.h6" Class="mb-3">توضیحات محصول</MudText>
|
||||
<MudDivider Class="mb-4" />
|
||||
<div class="blog-content">
|
||||
@((MarkupString)_product.FullInfo)
|
||||
</div>
|
||||
</MudPaper>
|
||||
}
|
||||
}
|
||||
</MudContainer>
|
||||
@@ -0,0 +1,64 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using FrontOffice.Main.Utilities;
|
||||
|
||||
namespace FrontOffice.Main.Pages.DiscountStore;
|
||||
|
||||
public partial class ProductDetail
|
||||
{
|
||||
[Parameter] public long Id { get; set; }
|
||||
|
||||
[Inject] private DiscountProductService DiscountProductService { get; set; } = default!;
|
||||
[Inject] private DiscountCartService DiscountCartService { get; set; } = default!;
|
||||
|
||||
private DiscountProductDetail? _product;
|
||||
private string _selectedImage = string.Empty;
|
||||
private int _quantity = 1;
|
||||
private bool _loading = true;
|
||||
private bool _addingToCart;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await LoadProductAsync();
|
||||
}
|
||||
|
||||
private async Task LoadProductAsync()
|
||||
{
|
||||
_loading = true;
|
||||
try
|
||||
{
|
||||
_product = await DiscountProductService.GetByIdAsync(Id);
|
||||
if (_product is not null)
|
||||
{
|
||||
_selectedImage = _product.Images.FirstOrDefault()?.ImageUrl
|
||||
?? _product.ThumbnailUrl;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
Snackbar.Add("خطا در بارگذاری محصول", MudBlazor.Severity.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task AddToCart()
|
||||
{
|
||||
if (_product is null) return;
|
||||
_addingToCart = true;
|
||||
try
|
||||
{
|
||||
await DiscountCartService.AddAsync(_product.Id, _quantity);
|
||||
Snackbar.Add($"{_product.Title} به سبد خرید اضافه شد", MudBlazor.Severity.Success);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Snackbar.Add("خطا در افزودن به سبد خرید", MudBlazor.Severity.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_addingToCart = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
@attribute [Route(RouteConstants.DiscountStore.Products)]
|
||||
|
||||
<PageTitle>فروشگاه تخفیفی</PageTitle>
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
||||
<!-- Hero -->
|
||||
<MudPaper Class="discount-hero pa-6 pa-md-8 mb-4 rounded-xl" Elevation="0">
|
||||
<MudStack AlignItems="AlignItems.Center" Spacing="2">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Loyalty" Size="Size.Large" Class="dash-hero-name" />
|
||||
<MudText Typo="Typo.h5" Align="Align.Center" Class="dash-hero-name">فروشگاه تخفیفی</MudText>
|
||||
<MudText Typo="Typo.body2" Align="Align.Center" Class="dash-hero-sub">
|
||||
خرید با استفاده از موجودی کیف پول تخفیفی
|
||||
</MudText>
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
|
||||
<!-- Filters -->
|
||||
<MudPaper Elevation="1" Class="pa-3 pa-md-4 mb-4 rounded-lg">
|
||||
<MudGrid Spacing="2" AlignItems="AlignItems.Center">
|
||||
<MudItem xs="12" sm="5">
|
||||
<MudTextField @bind-Value="_search"
|
||||
Placeholder="جستجو در محصولات..."
|
||||
AdornmentIcon="@Icons.Material.Filled.Search"
|
||||
Adornment="Adornment.Start"
|
||||
Immediate="true"
|
||||
OnKeyUp="OnSearchKeyUp"
|
||||
Variant="Variant.Outlined"
|
||||
Margin="Margin.Dense" />
|
||||
</MudItem>
|
||||
<MudItem xs="8" sm="5">
|
||||
<MudSelect T="long?" Value="_selectedCategoryId" ValueChanged="OnCategoryChanged"
|
||||
Label="دستهبندی" Variant="Variant.Outlined" Margin="Margin.Dense"
|
||||
Clearable="true" AnchorOrigin="Origin.BottomCenter">
|
||||
@foreach (var cat in _categories)
|
||||
{
|
||||
<MudSelectItem T="long?" Value="@((long?)cat.Id)">@cat.Title (@cat.ProductCount)</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="4" sm="2" Class="d-flex gap-2">
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" FullWidth="true"
|
||||
StartIcon="@Icons.Material.Filled.Search" OnClick="SearchProducts"
|
||||
Style="height:40px;">جستجو</MudButton>
|
||||
</MudItem>
|
||||
<MudItem xs="12" Class="d-flex justify-start justify-md-end gap-2">
|
||||
<MudButton Size="Size.Small" Variant="Variant.Filled" Color="Color.Primary"
|
||||
StartIcon="@Icons.Material.Filled.ShoppingCart"
|
||||
Href="@RouteConstants.DiscountStore.Cart">
|
||||
سبد خرید
|
||||
@if (DiscountCart.Count > 0)
|
||||
{
|
||||
<MudBadge Content="@DiscountCart.Count" Color="Color.Error" Overlap="true" Class="ms-2" />
|
||||
}
|
||||
</MudButton>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudPaper>
|
||||
|
||||
<!-- Loading -->
|
||||
@if (_loading)
|
||||
{
|
||||
<MudStack AlignItems="AlignItems.Center" Class="py-8">
|
||||
<MudProgressCircular Color="Color.Primary" Indeterminate="true" />
|
||||
<MudText Class="mt-2 mud-text-secondary">در حال بارگذاری...</MudText>
|
||||
</MudStack>
|
||||
}
|
||||
else if (_result.Products.Count == 0)
|
||||
{
|
||||
<MudAlert Severity="Severity.Info" Class="my-4">محصولی یافت نشد.</MudAlert>
|
||||
}
|
||||
else
|
||||
{
|
||||
<!-- Products Grid -->
|
||||
<MudGrid Spacing="1">
|
||||
@foreach (var p in _result.Products)
|
||||
{
|
||||
<MudItem xs="6" sm="6" md="3"
|
||||
onclick="@(() => NavigateToProduct(p.Id))">
|
||||
<MudCard Class="rounded-lg h-100 d-flex flex-column overflow-hidden"
|
||||
Style="cursor:pointer;height: 300px">
|
||||
<MudCardContent Class="d-flex flex-column pa-1 h-100">
|
||||
<div style="height: 60%;background-image: url('@(string.IsNullOrWhiteSpace(p.ThumbnailUrl) ? "/images/product-placeholder.svg" : p.ThumbnailUrl)');background-size: cover; background-position: center;border-radius: 0.5rem; position: relative;">
|
||||
@if (p.RemainingCount <= 0)
|
||||
{
|
||||
<div style="position:absolute;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.5);display:flex;align-items:center;justify-content:center;border-radius:0.5rem;">
|
||||
<MudChip T="string" Color="Color.Error" Variant="Variant.Filled" Size="Size.Small">ناموجود</MudChip>
|
||||
</div>
|
||||
}
|
||||
else if (p.RemainingCount <= 5)
|
||||
{
|
||||
<MudChip T="string" Color="Color.Warning" Variant="Variant.Filled" Size="Size.Small"
|
||||
Style="position:absolute;top:8px;right:8px;">
|
||||
فقط @p.RemainingCount عدد
|
||||
</MudChip>
|
||||
}
|
||||
@if (p.MaxDiscountPercent > 0)
|
||||
{
|
||||
<MudChip T="string" Color="Color.Error" Variant="Variant.Filled" Size="Size.Small"
|
||||
Style="position:absolute;top:8px;left:8px;">
|
||||
@p.MaxDiscountPercent% تخفیف
|
||||
</MudChip>
|
||||
}
|
||||
</div>
|
||||
<div class="pa-1 flex-grow-1 d-flex flex-column justify-space-between">
|
||||
<MudText Typo="Typo.subtitle1">@p.Title</MudText>
|
||||
<MudText Typo="Typo.subtitle2" Color="Color.Primary">@FormatPrice(p.Price)</MudText>
|
||||
</div>
|
||||
</MudCardContent>
|
||||
<MudCardActions Class="mt-auto d-flex justify-space-between pa-2">
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary"
|
||||
OnClick="@(() => AddToCart(p))"
|
||||
StartIcon="@Icons.Material.Filled.AddShoppingCart"
|
||||
Disabled="@(p.RemainingCount <= 0)">
|
||||
@(p.RemainingCount <= 0 ? "ناموجود" : "افزودن")
|
||||
</MudButton>
|
||||
</MudCardActions>
|
||||
</MudCard>
|
||||
</MudItem>
|
||||
}
|
||||
</MudGrid>
|
||||
|
||||
<!-- Pagination -->
|
||||
@if (_result.TotalPages > 1)
|
||||
{
|
||||
<div class="d-flex justify-center mt-4">
|
||||
<MudPagination Count="@_result.TotalPages" Selected="@_currentPage"
|
||||
SelectedChanged="OnPageChanged"
|
||||
Color="Color.Primary" Variant="Variant.Filled"
|
||||
BoundaryCount="1" MiddleCount="3" />
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</MudContainer>
|
||||
@@ -0,0 +1,87 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using FrontOffice.Main.Utilities;
|
||||
|
||||
namespace FrontOffice.Main.Pages.DiscountStore;
|
||||
|
||||
public partial class Products : ComponentBase, IDisposable
|
||||
{
|
||||
[Inject] private DiscountProductService ProductService { get; set; } = default!;
|
||||
[Inject] private DiscountCartService DiscountCart { get; set; } = default!;
|
||||
|
||||
private string _search = string.Empty;
|
||||
private long? _selectedCategoryId;
|
||||
private int _currentPage = 1;
|
||||
private bool _loading;
|
||||
private const int PageSize = 12;
|
||||
|
||||
private DiscountProductListResult _result = new(new(), 0, 0, 1);
|
||||
private List<DiscountCategoryNode> _categories = new();
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
_loading = true;
|
||||
await DiscountCart.EnsureInitializedAsync();
|
||||
DiscountCart.OnChange += StateHasChanged;
|
||||
var categoriesTask = ProductService.GetCategoriesAsync();
|
||||
var productsTask = ProductService.GetProductsAsync(page: 1, pageSize: PageSize);
|
||||
await Task.WhenAll(categoriesTask, productsTask);
|
||||
_categories = categoriesTask.Result;
|
||||
_result = productsTask.Result;
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
private async Task LoadProductsAsync()
|
||||
{
|
||||
_loading = true;
|
||||
StateHasChanged();
|
||||
_result = await ProductService.GetProductsAsync(
|
||||
page: _currentPage,
|
||||
pageSize: PageSize,
|
||||
search: string.IsNullOrWhiteSpace(_search) ? null : _search,
|
||||
categoryId: _selectedCategoryId);
|
||||
_loading = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task SearchProducts() => await LoadProductsAsync();
|
||||
|
||||
private async Task OnSearchKeyUp(KeyboardEventArgs e)
|
||||
{
|
||||
if (e.Key == "Enter")
|
||||
{
|
||||
_currentPage = 1;
|
||||
await LoadProductsAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OnCategoryChanged(long? value)
|
||||
{
|
||||
_selectedCategoryId = value;
|
||||
_currentPage = 1;
|
||||
await LoadProductsAsync();
|
||||
}
|
||||
|
||||
private async Task OnPageChanged(int page)
|
||||
{
|
||||
_currentPage = page;
|
||||
await LoadProductsAsync();
|
||||
}
|
||||
|
||||
private async Task AddToCart(DiscountProductCard p)
|
||||
{
|
||||
await DiscountCart.AddAsync(p.Id);
|
||||
}
|
||||
|
||||
private void NavigateToProduct(long id)
|
||||
{
|
||||
Navigation.NavigateTo($"{RouteConstants.DiscountStore.ProductDetail}{id}");
|
||||
}
|
||||
|
||||
private static string FormatPrice(long price) => $"{price:N0} تومان";
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
DiscountCart.OnChange -= StateHasChanged;
|
||||
}
|
||||
}
|
||||
@@ -116,7 +116,7 @@
|
||||
<!-- Contact Support -->
|
||||
<section class="py-12 bg-grey-50">
|
||||
<MudContainer MaxWidth="MaxWidth.Large">
|
||||
<MudPaper Elevation="3" Class="pa-8 text-center">
|
||||
<MudPaper Elevation="2" Class="pa-8 text-center">
|
||||
<MudIcon Icon="@Icons.Material.Filled.ContactSupport" Size="Size.Large" Color="Color.Primary" Class="mb-4" />
|
||||
<MudText Typo="Typo.h4" Class="mb-3">سوال شما پاسخ داده نشد؟</MudText>
|
||||
<MudText Typo="Typo.body1" Class="mud-text-secondary mb-6">
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
@attribute [Route(RouteConstants.Gateway.CartChooser)]
|
||||
|
||||
<PageTitle>سبد خرید</PageTitle>
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.Medium" Class="py-6 py-md-10">
|
||||
<PageHeader Title="سبد خرید" BackHref="@RouteConstants.Profile.Index" />
|
||||
<MudStack AlignItems="AlignItems.Center" Spacing="4">
|
||||
<MudIcon Icon="@Icons.Material.Filled.ShoppingCart" Size="Size.Large" Color="Color.Primary" />
|
||||
<MudText Typo="Typo.body1" Align="Align.Center" Class="mud-text-secondary">
|
||||
سبد خرید کدام فروشگاه را میخواهید مشاهده کنید؟
|
||||
</MudText>
|
||||
|
||||
<MudGrid Spacing="4" Justify="Justify.Center" Class="mt-4">
|
||||
<MudItem xs="12" sm="6">
|
||||
<MudLink Href="@RouteConstants.Store.Cart" Underline="Underline.None" Class="store-chooser-link">
|
||||
<MudPaper Elevation="2" Class="pa-6 rounded-xl text-center store-chooser-card store-chooser-regular">
|
||||
<MudAvatar Size="Size.Large" Class="mx-auto mb-3 gateway-avatar-sm-store">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Storefront" Size="Size.Large" Class="text-brand-store" />
|
||||
</MudAvatar>
|
||||
<MudText Typo="Typo.h6" Class="fw-semibold">سبد خرید فروشگاه</MudText>
|
||||
@if (_cartCount > 0)
|
||||
{
|
||||
<MudChip T="string" Color="Color.Success" Variant="Variant.Filled" Size="Size.Small" Class="mt-2">
|
||||
@_cartCount محصول
|
||||
</MudChip>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary mt-1">سبد خالی</MudText>
|
||||
}
|
||||
</MudPaper>
|
||||
</MudLink>
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12" sm="6">
|
||||
<MudLink Href="@RouteConstants.DiscountStore.Cart" Underline="Underline.None" Class="store-chooser-link">
|
||||
<MudPaper Elevation="2" Class="pa-6 rounded-xl text-center store-chooser-card store-chooser-discount">
|
||||
<MudAvatar Size="Size.Large" Class="mx-auto mb-3 gateway-avatar-sm-discount">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Loyalty" Size="Size.Large" Class="text-brand-discount" />
|
||||
</MudAvatar>
|
||||
<MudText Typo="Typo.h6" Class="fw-semibold">سبد خرید تخفیفی</MudText>
|
||||
@if (_discountCartCount > 0)
|
||||
{
|
||||
<MudChip T="string" Color="Color.Error" Variant="Variant.Filled" Size="Size.Small" Class="mt-2">
|
||||
@_discountCartCount محصول
|
||||
</MudChip>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary mt-1">سبد خالی</MudText>
|
||||
}
|
||||
</MudPaper>
|
||||
</MudLink>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
|
||||
<MudButton Variant="Variant.Text" StartIcon="@Icons.Material.Filled.ArrowBack"
|
||||
OnClick="GoBack" Class="mt-4">
|
||||
بازگشت به داشبورد
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
</MudContainer>
|
||||
@@ -0,0 +1,47 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.JSInterop;
|
||||
using FrontOffice.Main.Utilities;
|
||||
|
||||
namespace FrontOffice.Main.Pages.Gateway;
|
||||
|
||||
public partial class CartChooser : IDisposable
|
||||
{
|
||||
[Inject] private CartService CartService { get; set; } = default!;
|
||||
[Inject] private DiscountCartService DiscountCartService { get; set; } = default!;
|
||||
[Inject] private IJSRuntime JS { get; set; } = default!;
|
||||
|
||||
private int _cartCount;
|
||||
private int _discountCartCount;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await CartService.EnsureInitializedAsync();
|
||||
await DiscountCartService.EnsureInitializedAsync();
|
||||
|
||||
_cartCount = CartService.Count;
|
||||
_discountCartCount = DiscountCartService.Count;
|
||||
|
||||
CartService.OnChange += OnCartChanged;
|
||||
DiscountCartService.OnChange += OnDiscountCartChanged;
|
||||
}
|
||||
|
||||
private void OnCartChanged()
|
||||
{
|
||||
_cartCount = CartService.Count;
|
||||
InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
private void OnDiscountCartChanged()
|
||||
{
|
||||
_discountCartCount = DiscountCartService.Count;
|
||||
InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
CartService.OnChange -= OnCartChanged;
|
||||
DiscountCartService.OnChange -= OnDiscountCartChanged;
|
||||
}
|
||||
|
||||
private async Task GoBack() => await JS.InvokeVoidAsync("history.back");
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
@attribute [Route(RouteConstants.Gateway.OrdersChooser)]
|
||||
@inject IJSRuntime JS
|
||||
|
||||
<PageTitle>سفارشات من</PageTitle>
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.Medium" Class="py-6 py-md-10">
|
||||
<PageHeader Title="سفارشات من" BackHref="@RouteConstants.Profile.Index" />
|
||||
<MudStack AlignItems="AlignItems.Center" Spacing="4">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Receipt" Size="Size.Large" Color="Color.Primary" />
|
||||
<MudText Typo="Typo.body1" Align="Align.Center" Class="mud-text-secondary">
|
||||
سفارشات کدام فروشگاه را میخواهید مشاهده کنید؟
|
||||
</MudText>
|
||||
|
||||
<MudGrid Spacing="4" Justify="Justify.Center" Class="mt-4">
|
||||
<MudItem xs="12" sm="6">
|
||||
<MudLink Href="@RouteConstants.Store.Orders" Underline="Underline.None" Class="store-chooser-link">
|
||||
<MudPaper Elevation="2" Class="pa-6 rounded-xl text-center store-chooser-card store-chooser-regular">
|
||||
<MudAvatar Size="Size.Large" Class="mx-auto mb-3 gateway-avatar-sm-store">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Storefront" Size="Size.Large" Class="text-brand-store" />
|
||||
</MudAvatar>
|
||||
<MudText Typo="Typo.h6" Class="fw-semibold">سفارشات فروشگاه</MudText>
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary mt-1">سفارشات خرید معمولی</MudText>
|
||||
</MudPaper>
|
||||
</MudLink>
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12" sm="6">
|
||||
<MudLink Href="@RouteConstants.DiscountStore.Orders" Underline="Underline.None" Class="store-chooser-link">
|
||||
<MudPaper Elevation="2" Class="pa-6 rounded-xl text-center store-chooser-card store-chooser-discount">
|
||||
<MudAvatar Size="Size.Large" Class="mx-auto mb-3 gateway-avatar-sm-discount">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Loyalty" Size="Size.Large" Class="text-brand-discount" />
|
||||
</MudAvatar>
|
||||
<MudText Typo="Typo.h6" Class="fw-semibold">سفارشات فروشگاه تخفیفی</MudText>
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary mt-1">سفارشات خرید تخفیفی</MudText>
|
||||
</MudPaper>
|
||||
</MudLink>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
|
||||
<MudButton Variant="Variant.Text" StartIcon="@Icons.Material.Filled.ArrowBack"
|
||||
OnClick="GoBack" Class="mt-4">
|
||||
بازگشت به داشبورد
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
</MudContainer>
|
||||
|
||||
@code {
|
||||
private async Task GoBack() => await JS.InvokeVoidAsync("history.back");
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
@attribute [Route(RouteConstants.Gateway.StoreChooser)]
|
||||
|
||||
<PageTitle>فروشگاهها</PageTitle>
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.Medium" Class="py-6 py-md-10">
|
||||
<PageHeader Title="انتخاب فروشگاه" BackHref="@RouteConstants.Profile.Index" />
|
||||
<MudStack AlignItems="AlignItems.Center" Spacing="4">
|
||||
<MudText Typo="Typo.body1" Align="Align.Center" Class="mud-text-secondary" Style="max-width:500px;">
|
||||
برای خرید، یکی از فروشگاهها را انتخاب کنید. هر فروشگاه سبد خرید و سفارشات مجزای خود را دارد.
|
||||
</MudText>
|
||||
|
||||
<MudGrid Spacing="4" Justify="Justify.Center" Class="mt-4">
|
||||
<!-- Regular Store -->
|
||||
<MudItem xs="12" sm="6">
|
||||
<MudLink Href="@RouteConstants.Store.Products" Underline="Underline.None" Class="store-chooser-link">
|
||||
<MudPaper Elevation="2" Class="pa-6 rounded-xl text-center store-chooser-card store-chooser-regular">
|
||||
<MudAvatar Size="Size.Large" Class="mx-auto mb-3 gateway-avatar-store">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Storefront" Size="Size.Large" Class="text-brand-store" />
|
||||
</MudAvatar>
|
||||
<MudText Typo="Typo.h5" Class="fw-bold">فروشگاه</MudText>
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary mt-2">
|
||||
خرید محصولات با پرداخت از کیف پول اعتباری
|
||||
</MudText>
|
||||
@if (_cartCount > 0)
|
||||
{
|
||||
<MudChip T="string" Color="Color.Success" Variant="Variant.Filled" Size="Size.Small" Class="mt-3">
|
||||
@_cartCount محصول در سبد خرید
|
||||
</MudChip>
|
||||
}
|
||||
</MudPaper>
|
||||
</MudLink>
|
||||
</MudItem>
|
||||
|
||||
<!-- Discount Store -->
|
||||
<MudItem xs="12" sm="6">
|
||||
<MudLink Href="@RouteConstants.DiscountStore.Products" Underline="Underline.None" Class="store-chooser-link">
|
||||
<MudPaper Elevation="2" Class="pa-6 rounded-xl text-center store-chooser-card store-chooser-discount">
|
||||
<MudAvatar Size="Size.Large" Class="mx-auto mb-3 gateway-avatar-discount">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Loyalty" Size="Size.Large" Class="text-brand-discount" />
|
||||
</MudAvatar>
|
||||
<MudText Typo="Typo.h5" Class="fw-bold">فروشگاه تخفیفی</MudText>
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary mt-2">
|
||||
خرید با استفاده از موجودی کیف تخفیفی + درگاه پرداخت
|
||||
</MudText>
|
||||
@if (_discountCartCount > 0)
|
||||
{
|
||||
<MudChip T="string" Color="Color.Error" Variant="Variant.Filled" Size="Size.Small" Class="mt-3">
|
||||
@_discountCartCount محصول در سبد خرید
|
||||
</MudChip>
|
||||
}
|
||||
</MudPaper>
|
||||
</MudLink>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudStack>
|
||||
</MudContainer>
|
||||
@@ -0,0 +1,43 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using FrontOffice.Main.Utilities;
|
||||
|
||||
namespace FrontOffice.Main.Pages.Gateway;
|
||||
|
||||
public partial class StoreChooser : IDisposable
|
||||
{
|
||||
[Inject] private CartService CartService { get; set; } = default!;
|
||||
[Inject] private DiscountCartService DiscountCartService { get; set; } = default!;
|
||||
|
||||
private int _cartCount;
|
||||
private int _discountCartCount;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await CartService.EnsureInitializedAsync();
|
||||
await DiscountCartService.EnsureInitializedAsync();
|
||||
|
||||
_cartCount = CartService.Count;
|
||||
_discountCartCount = DiscountCartService.Count;
|
||||
|
||||
CartService.OnChange += OnCartChanged;
|
||||
DiscountCartService.OnChange += OnDiscountCartChanged;
|
||||
}
|
||||
|
||||
private void OnCartChanged()
|
||||
{
|
||||
_cartCount = CartService.Count;
|
||||
InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
private void OnDiscountCartChanged()
|
||||
{
|
||||
_discountCartCount = DiscountCartService.Count;
|
||||
InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
CartService.OnChange -= OnCartChanged;
|
||||
DiscountCartService.OnChange -= OnDiscountCartChanged;
|
||||
}
|
||||
}
|
||||
@@ -1,354 +1,343 @@
|
||||
@attribute [Route(RouteConstants.Main.MainPage)]
|
||||
@inject NavigationManager Navigation
|
||||
@inject IJSRuntime JS
|
||||
|
||||
<PageTitle>صفحه اصلی</PageTitle>
|
||||
<PageTitle>صفحه اصلی | کارا بازار سلامت</PageTitle>
|
||||
|
||||
<!-- HERO -->
|
||||
<section id="hero" class="hero-section">
|
||||
<MudContainer MaxWidth="MaxWidth.Large" Class="py-16">
|
||||
<MudGrid Justify="Justify.Center" Spacing="3">
|
||||
<MudItem xs="12" md="6">
|
||||
<MudChip T="string" Color="Color.Secondary" Variant="Variant.Filled" Class="mb-3">پیشنهاد ویژه برای شروع</MudChip>
|
||||
<MudStack Spacing="2">
|
||||
<MudText Typo="Typo.h1">
|
||||
با دعوت از دوستان خود، از خریدهای واقعی پاداش بگیرید
|
||||
</MudText>
|
||||
<MudText Typo="Typo.body1" Class="mud-text-secondary mb-6">
|
||||
ثبتنام آسان، تجربهٔ سریع و شفاف. همین امروز حساب بساز و با دعوت دوستانت، امتیاز و پاداش دریافت کن.
|
||||
</MudText>
|
||||
@* ═══════════════════════════════════════════════
|
||||
1. HERO — Bold, minimal, centered
|
||||
═══════════════════════════════════════════════ *@
|
||||
<section class="hero-gradient py-12 py-md-16">
|
||||
<MudContainer MaxWidth="MaxWidth.Medium">
|
||||
<MudStack AlignItems="AlignItems.Center" Spacing="4" Class="text-center">
|
||||
<MudChip T="string" Color="Color.Default" Variant="Variant.Filled"
|
||||
Class="pulse-chip hero-chip-glass" Size="Size.Small">
|
||||
🌿 پلتفرم سلامتمحور فروش و تیمسازی
|
||||
</MudChip>
|
||||
|
||||
<MudStack Row="true" Spacing="3" Class="mb-2">
|
||||
<MudButton Variant="Variant.Outlined"
|
||||
Color="Color.Primary"
|
||||
Size="Size.Large"
|
||||
Class="mud-ripple rounded-pill"
|
||||
OnClick="@(() => Navigation.NavigateTo("/pricing"))">مشاهده قیمتها</MudButton>
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
Size="Size.Large"
|
||||
Class="mud-ripple rounded-pill"
|
||||
OnClick="@(() => NavigateToRegistrationWizard())">شروع کن</MudButton>
|
||||
</MudStack>
|
||||
<MudDivider Class="my-2" Style="width: 90%;" />
|
||||
<MudStack Row="true" Spacing="1">
|
||||
<MudChip T="string"
|
||||
Color="Color.Info"
|
||||
Variant="Variant.Outlined"
|
||||
Class="mb-3">ثبتنام زیر ۲ دقیقه</MudChip>
|
||||
<MudChip T="string"
|
||||
Color="Color.Info"
|
||||
Variant="Variant.Outlined"
|
||||
Class="mb-3">پشتیبانی ۷×۲۴</MudChip>
|
||||
<MudChip T="string"
|
||||
Color="Color.Info"
|
||||
Variant="Variant.Outlined"
|
||||
Class="mb-3">پرداخت ایمن</MudChip>
|
||||
<MudText Typo="Typo.h1" Class="hero-title" Style="max-width:680px;">
|
||||
رشد تیم، فروش واقعی، پاداش شفاف
|
||||
</MudText>
|
||||
|
||||
<MudText Typo="Typo.body1" Class="hero-subtitle" Style="max-width:520px;">
|
||||
با دعوت از دوستان، از خریدهای واقعی محصولات سلامت پاداش بگیرید. ثبتنام سریع، داشبورد لحظهای.
|
||||
</MudText>
|
||||
|
||||
<MudStack Row="true" Spacing="2" Class="mt-2 flex-wrap" Justify="Justify.Center">
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Class="rounded-pill hero-cta-primary"
|
||||
Size="Size.Large"
|
||||
OnClick="NavigateToRegistrationWizard">
|
||||
شروع رایگان
|
||||
</MudButton>
|
||||
<MudButton Variant="Variant.Outlined"
|
||||
Class="rounded-pill hero-cta-outline"
|
||||
Size="Size.Large"
|
||||
OnClick="@(() => Navigation.NavigateTo("/blog"))">
|
||||
آخرین اخبار
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
|
||||
@* ── Trust badges ── *@
|
||||
<MudStack Row="true" Spacing="4" Class="mt-4 flex-wrap" Justify="Justify.Center" AlignItems="AlignItems.Center">
|
||||
@foreach (var badge in _trustBadges)
|
||||
{
|
||||
<MudStack Row="true" Spacing="1" AlignItems="AlignItems.Center">
|
||||
<MudIcon Icon="@badge.Icon" Size="Size.Small" Style="color:rgba(255,255,255,.7);" />
|
||||
<MudText Typo="Typo.caption" Style="color:rgba(255,255,255,.8);">@badge.Text</MudText>
|
||||
</MudStack>
|
||||
}
|
||||
</MudStack>
|
||||
</MudStack>
|
||||
</MudContainer>
|
||||
</section>
|
||||
|
||||
@* ═══════════════════════════════════════════════
|
||||
1b. FEATURED POST BANNER — below hero
|
||||
═══════════════════════════════════════════════ *@
|
||||
@if (_latestPosts.Any())
|
||||
{
|
||||
var featured = _latestPosts.First();
|
||||
<section class="py-4" style="background:var(--mud-palette-surface);">
|
||||
<MudContainer MaxWidth="MaxWidth.Medium">
|
||||
<MudPaper Elevation="1" Class="landing-blog-banner rounded-xl cursor-pointer pa-3"
|
||||
@onclick="() => NavigateToPost(featured.Slug)">
|
||||
<MudStack Row="true" Spacing="3" AlignItems="AlignItems.Center">
|
||||
@if (!string.IsNullOrWhiteSpace(featured.ThumbnailUrl))
|
||||
{
|
||||
<AppImage Path="@featured.ThumbnailUrl"
|
||||
Alt="@featured.Title"
|
||||
ObjectFit="ObjectFit.Cover"
|
||||
Class="landing-blog-thumb"
|
||||
Fallback="@Icons.Material.Filled.Article" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudAvatar Variant="Variant.Filled" Color="Color.Primary" Size="Size.Large" Class="rounded-lg" Style="min-width:64px;">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Article" />
|
||||
</MudAvatar>
|
||||
}
|
||||
<MudStack Spacing="1" Style="flex:1; min-width:0;">
|
||||
<MudChip T="string" Size="Size.Small" Variant="Variant.Outlined" Color="Color.Primary">
|
||||
🔥 جدیدترین مطلب
|
||||
</MudChip>
|
||||
<MudText Typo="Typo.subtitle1" Class="landing-blog-title">@featured.Title</MudText>
|
||||
@if (!string.IsNullOrWhiteSpace(featured.Summary))
|
||||
{
|
||||
<MudText Typo="Typo.body2" Class="landing-blog-summary mud-text-secondary">@featured.Summary</MudText>
|
||||
}
|
||||
</MudStack>
|
||||
<MudIcon Icon="@Icons.Material.Filled.ChevronLeft" Class="mud-text-secondary d-none d-sm-flex" />
|
||||
</MudStack>
|
||||
</MudItem>
|
||||
</MudPaper>
|
||||
</MudContainer>
|
||||
</section>
|
||||
}
|
||||
|
||||
<MudItem xs="12" md="6">
|
||||
<MudPaper Class="pa-8 rounded-xl" Style="background: radial-gradient(600px 280px at 120% 0, #daccff 0, transparent 60%), radial-gradient(600px 280px at -10% 100%, #ffe2f2 0, transparent 60%), linear-gradient(180deg, #fff, #fbfaff);">
|
||||
<MudImage Src="images/team-meeting.jpg"
|
||||
Alt="جلسه تیم فروش"
|
||||
ObjectFit="ObjectFit.Cover"
|
||||
ObjectPosition="ObjectPosition.Center"
|
||||
Style="width:100%"
|
||||
Class="rounded-xl" />
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
@* ═══════════════════════════════════════════════
|
||||
2. HOW IT WORKS — 3 numbered steps
|
||||
═══════════════════════════════════════════════ *@
|
||||
<section class="section-landing">
|
||||
<MudContainer MaxWidth="MaxWidth.Large">
|
||||
<div class="text-center mb-8 fade-in-up">
|
||||
<MudText Typo="Typo.h3">سه گام تا شروع</MudText>
|
||||
<MudText Typo="Typo.body1" Class="mud-text-secondary mt-2">
|
||||
بدون پیچیدگی، سریع و آسان
|
||||
</MudText>
|
||||
</div>
|
||||
|
||||
<MudGrid Spacing="4" Justify="Justify.Center">
|
||||
@foreach (var (step, i) in _steps.Select((s, i) => (s, i)))
|
||||
{
|
||||
<MudItem xs="12" sm="4">
|
||||
<MudPaper Elevation="0" Class="pa-5 rounded-xl feature-card-v2 text-center fade-in-up" data-delay="@(i * 150)">
|
||||
<MudAvatar Size="Size.Large" Color="Color.Primary" Variant="Variant.Filled" Class="mx-auto mb-3 landing-step-num">
|
||||
<MudText Typo="Typo.h5" Style="color:#fff;font-weight:800;">@(i + 1)</MudText>
|
||||
</MudAvatar>
|
||||
<MudText Typo="Typo.h6" Class="mb-1">@step.Title</MudText>
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary">@step.Desc</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
}
|
||||
</MudGrid>
|
||||
</MudContainer>
|
||||
</section>
|
||||
<MudStack Spacing="4">
|
||||
|
||||
<!-- WHYUS -->
|
||||
<section class="whyus-section">
|
||||
<MudContainer MaxWidth="MaxWidth.Large" Class="whyus-section">
|
||||
|
||||
<MudText Typo="Typo.h4" GutterBottom="true">
|
||||
چرا کاربران ما را انتخاب میکنند؟
|
||||
</MudText>
|
||||
<MudText Typo="Typo.subtitle1" Class="mud-text-secondary mb-8">
|
||||
سه دلیل روشن برای شروع امروز.
|
||||
@* ═══════════════════════════════════════════════
|
||||
3. FEATURES — 6 cards with icons
|
||||
═══════════════════════════════════════════════ *@
|
||||
<section class="section-landing" style="background:var(--mud-palette-background-gray);">
|
||||
<MudContainer MaxWidth="MaxWidth.Large">
|
||||
<div class="text-center mb-8 fade-in-up">
|
||||
<MudText Typo="Typo.h3">چرا کارا بازار سلامت؟</MudText>
|
||||
<MudText Typo="Typo.body1" Class="mud-text-secondary mt-2" Style="max-width:540px;margin:0 auto;">
|
||||
ابزارهایی ساده و قدرتمند برای رشد کسبوکار شما
|
||||
</MudText>
|
||||
</div>
|
||||
|
||||
<MudGrid Spacing="3" Justify="Justify.Center">
|
||||
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudCard Class="feature-card rounded-xl pa-4" Outlined="true">
|
||||
<MudStack>
|
||||
<MudStack Row="true">
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.VerifiedUser"
|
||||
Variant="Variant.Outlined"
|
||||
Color="Color.Primary"
|
||||
Size="Size.Small" />
|
||||
<MudText Typo="Typo.h6" Align="Align.Center" Class="mb-2">ثبتنام سریع و ساده</MudText>
|
||||
</MudStack>
|
||||
|
||||
<MudText Class="ps-10 mud-text-secondary">
|
||||
در چند گام کوتاه، حسابت را بساز و شروع کن.
|
||||
</MudText>
|
||||
</MudStack>
|
||||
</MudCard>
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudCard Class="feature-card rounded-xl pa-4" Outlined="true">
|
||||
<MudStack>
|
||||
<MudStack Row="true">
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.StarBorder"
|
||||
Variant="Variant.Outlined"
|
||||
Color="Color.Primary"
|
||||
Size="Size.Small" />
|
||||
<MudText Typo="Typo.h6" Align="Align.Center" Class="mb-2">مزایا و پاداشهای شفاف</MudText>
|
||||
</MudStack>
|
||||
|
||||
<MudText Class="ps-10 mud-text-secondary">
|
||||
قوانین روشن، دسترسی آسان به سوابق و گزارشها.
|
||||
</MudText>
|
||||
</MudStack>
|
||||
</MudCard>
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudCard Class="feature-card rounded-xl pa-4" Outlined="true">
|
||||
<MudStack>
|
||||
<MudStack Row="true">
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.Devices"
|
||||
Variant="Variant.Outlined"
|
||||
Color="Color.Primary"
|
||||
Size="Size.Small" />
|
||||
<MudText Typo="Typo.h6" Align="Align.Center" Class="mb-2">اپلیکیشن واکنشگرا</MudText>
|
||||
</MudStack>
|
||||
|
||||
<MudText Class="ps-10 mud-text-secondary">
|
||||
تجربهای روان در موبایل و دسکتاپ.
|
||||
</MudText>
|
||||
</MudStack>
|
||||
</MudCard>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
|
||||
</MudContainer>
|
||||
</section>
|
||||
|
||||
@* <!-- FEATURES: معرفی پکیجها -->
|
||||
<section id="features" class="py-20">
|
||||
<MudContainer MaxWidth="MaxWidth.Large">
|
||||
<MudText Typo="Typo.h4" GutterBottom="true">
|
||||
پکیجهای سرمایه گذاری
|
||||
</MudText>
|
||||
<MudText Typo="Typo.subtitle1" Class="mud-text-secondary mb-8">
|
||||
بر اساس اندازهٔ تیم خود انتخاب کنید.
|
||||
</MudText>
|
||||
@if (_isLoadingPackages)
|
||||
<MudGrid Spacing="3" Justify="Justify.Center">
|
||||
@foreach (var (icon, title, desc, delay) in _features)
|
||||
{
|
||||
<MudStack AlignItems="AlignItems.Center" Class="py-8">
|
||||
<MudProgressCircular Color="Color.Primary" Indeterminate="true" Size="Size.Large" />
|
||||
<MudText Typo="Typo.body1" Class="mud-text-secondary mt-2">در حال بارگذاری پکیجها...</MudText>
|
||||
</MudStack>
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudPaper Elevation="0" Class="pa-5 rounded-xl feature-card-v2 fade-in-up" data-delay="@delay">
|
||||
<MudStack Spacing="2">
|
||||
<MudAvatar Color="Color.Primary" Variant="Variant.Filled" Size="Size.Medium" Class="mb-1">
|
||||
<MudIcon Icon="@icon" />
|
||||
</MudAvatar>
|
||||
<MudText Typo="Typo.h6">@title</MudText>
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary">@desc</MudText>
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
}
|
||||
else if (_packs.Any())
|
||||
</MudGrid>
|
||||
</MudContainer>
|
||||
</section>
|
||||
|
||||
@* ═══════════════════════════════════════════════
|
||||
4. STATS — Counter cards
|
||||
═══════════════════════════════════════════════ *@
|
||||
<section class="section-landing">
|
||||
<MudContainer MaxWidth="MaxWidth.Medium">
|
||||
<MudGrid Spacing="3" Justify="Justify.Center">
|
||||
@foreach (var stat in _stats)
|
||||
{
|
||||
<MudGrid Spacing="4" Justify="Justify.Center" Class="stretch-grid">
|
||||
@foreach (var p in _packs)
|
||||
{
|
||||
<MudItem xs="12" md="4" Class="d-flex">
|
||||
<MudCard>
|
||||
<MudCardMedia Image="@p.Image"
|
||||
Title="@p.Title"
|
||||
Height="300" />
|
||||
<MudCardContent>
|
||||
<MudText Typo="Typo.h5" Class="mb-3">@(p.Title)</MudText>
|
||||
@((MarkupString)p.Body)
|
||||
</MudCardContent>
|
||||
<MudCardActions Class="ma-2">
|
||||
<MudText Typo="Typo.h5"
|
||||
Color="Color.Success"
|
||||
Align="Align.End">@(p.Price.ToThousands().ToCurrencyUnitIRT())</MudText>
|
||||
<MudItem xs="6" sm="3">
|
||||
<MudPaper Elevation="2" Class="pa-4 rounded-xl stat-card fade-in-up text-center">
|
||||
<MudText Class="stat-number" Color="@stat.Color" id="@stat.ElementId">@stat.Display</MudText>
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary mt-1">@stat.Label</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
}
|
||||
</MudGrid>
|
||||
</MudContainer>
|
||||
</section>
|
||||
|
||||
@* ═══════════════════════════════════════════════
|
||||
5. LATEST BLOG POSTS
|
||||
═══════════════════════════════════════════════ *@
|
||||
@if (_latestPosts.Any())
|
||||
{
|
||||
<section class="section-landing" style="background:var(--mud-palette-background-gray);">
|
||||
<MudContainer MaxWidth="MaxWidth.Large">
|
||||
<div class="text-center mb-8 fade-in-up">
|
||||
<MudText Typo="Typo.h3">آخرین مقالات</MudText>
|
||||
<MudText Typo="Typo.body1" Class="mud-text-secondary mt-2">
|
||||
جدیدترین مطالب و اخبار کارا بازار سلامت
|
||||
</MudText>
|
||||
</div>
|
||||
|
||||
<MudGrid Spacing="4" Justify="Justify.Center">
|
||||
@foreach (var post in _latestPosts.Take(2))
|
||||
{
|
||||
<MudItem xs="12" sm="6" md="6">
|
||||
<MudPaper Elevation="1" Class="rounded-xl blog-card-v2 cursor-pointer fade-in-up" Style="overflow:hidden;"
|
||||
@onclick="() => NavigateToPost(post.Slug)">
|
||||
<div class="blog-thumb" style="height:180px; background:#f0f0f0;">
|
||||
@if (!string.IsNullOrWhiteSpace(post.ThumbnailUrl))
|
||||
{
|
||||
<AppImage Path="@post.ThumbnailUrl"
|
||||
Alt="@post.Title"
|
||||
ObjectFit="ObjectFit.Cover"
|
||||
Style="width:100%; height:180px;" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<div style="height:180px; display:flex; align-items:center; justify-content:center; background:linear-gradient(135deg,#6366f1,#a78bfa);">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Article" Size="Size.Large" Style="color:rgba(255,255,255,.5);" />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="pa-4">
|
||||
@if (post.Categories.Any())
|
||||
{
|
||||
<MudChip T="string" Size="Size.Small" Variant="Variant.Outlined" Color="Color.Primary" Class="mb-2">
|
||||
@post.Categories.First().Title
|
||||
</MudChip>
|
||||
}
|
||||
<MudText Typo="Typo.h6" Class="mb-1" Style="display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden;">
|
||||
@post.Title
|
||||
</MudText>
|
||||
@if (!string.IsNullOrWhiteSpace(post.Summary))
|
||||
{
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary mb-2" Style="display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden;">
|
||||
@post.Summary
|
||||
</MudText>
|
||||
}
|
||||
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center" Class="mt-2">
|
||||
@if (post.PublishedAt.HasValue)
|
||||
{
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">
|
||||
<MudIcon Icon="@Icons.Material.Filled.CalendarToday" Size="Size.Small" Class="ml-1" />
|
||||
@post.PublishedAt.Value.ToString("yyyy/MM/dd")
|
||||
</MudText>
|
||||
}
|
||||
<MudSpacer />
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
OnClick="@(() => NavigateToPackage(p.Id))">مشاهده جزئیات</MudButton>
|
||||
</MudCardActions>
|
||||
</MudCard>
|
||||
</MudItem>
|
||||
}
|
||||
</MudGrid>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudStack AlignItems="AlignItems.Center" Class="py-8">
|
||||
<MudText Typo="Typo.body1" Class="mud-text-secondary">هیچ پکیجی یافت نشد.</MudText>
|
||||
</MudStack>
|
||||
}
|
||||
</MudContainer>
|
||||
</section>
|
||||
*@
|
||||
<!-- STATS -->
|
||||
<section class="stats-strip">
|
||||
<MudContainer MaxWidth="MaxWidth.Large">
|
||||
<MudText Typo="Typo.h4" Align="Align.Center" GutterBottom="true">
|
||||
کارا بازار سلامت چطور کار میکند؟
|
||||
</MudText>
|
||||
<MudText Typo="Typo.subtitle1" Align="Align.Center" Class="mud-text-secondary mb-8">
|
||||
سه گام روشن تا شروع یک ماجراجویی جدید
|
||||
</MudText>
|
||||
<MudGrid>
|
||||
<MudItem xs="12" lg="6">
|
||||
<MudStack AlignItems="AlignItems.Start">
|
||||
<MudPaper Outlined="true" Class="pa-6 rounded-xl">
|
||||
<MudTimeline>
|
||||
<MudTimelineItem Color="Color.Info" Size="Size.Small">
|
||||
<ItemOpposite>
|
||||
<MudText Color="Color.Info" Typo="Typo.h5">ثبتنام</MudText>
|
||||
</ItemOpposite>
|
||||
<ItemContent>
|
||||
<MudText Color="Color.Info" Typo="Typo.h6" GutterBottom="true">مرحله اول</MudText>
|
||||
<MudText>یک حساب بساز و وارد شو.</MudText>
|
||||
</ItemContent>
|
||||
</MudTimelineItem>
|
||||
|
||||
<MudTimelineItem Color="Color.Success" Size="Size.Small">
|
||||
<ItemOpposite>
|
||||
<MudText Color="Color.Success" Typo="Typo.h5">دعوت دوستان</MudText>
|
||||
</ItemOpposite>
|
||||
<ItemContent>
|
||||
<MudText Align="Align.End" Color="Color.Success" Typo="Typo.h6" GutterBottom="true">مرحله دوم</MudText>
|
||||
<MudText Align="Align.End">لینک دعوتت را به اشتراک بگذار.</MudText>
|
||||
</ItemContent>
|
||||
</MudTimelineItem>
|
||||
|
||||
<MudTimelineItem Color="Color.Error" Size="Size.Small">
|
||||
<ItemOpposite>
|
||||
<MudText Color="Color.Error" Typo="Typo.h5">دریافت پاداش</MudText>
|
||||
</ItemOpposite>
|
||||
<ItemContent>
|
||||
<MudText Color="Color.Error" Typo="Typo.h6" GutterBottom="true">مرحله سوم</MudText>
|
||||
<MudText>از خریدهای واقعی دوستانت پاداش بگیر.</MudText>
|
||||
</ItemContent>
|
||||
</MudTimelineItem>
|
||||
</MudTimeline>
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Visibility" Size="Size.Small" Class="ml-1" />
|
||||
@post.ViewCount
|
||||
</MudText>
|
||||
</MudStack>
|
||||
</div>
|
||||
</MudPaper>
|
||||
</MudStack>
|
||||
</MudItem>
|
||||
<MudItem xs="1">
|
||||
<MudDivider Vertical="true" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" lg="5" Class="d-flex align-center">
|
||||
<MudStack>
|
||||
<MudGrid Spacing="4" Justify="Justify.SpaceAround">
|
||||
<MudItem xs="6">
|
||||
<MudPaper Elevation="2" Class="pa-4 rounded-xl ">
|
||||
<MudText Typo="Typo.h4" Align="Align.Center" Color="Color.Success">+۵۰٪</MudText>
|
||||
<MudText Typo="Typo.caption" HtmlTag="div" Align="Align.Center" Color="Color.Info">رشد میانگین تیم</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
<MudItem xs="6">
|
||||
<MudPaper Elevation="2" Class="pa-4 rounded-xl text-center">
|
||||
<MudText Typo="Typo.h4" Align="Align.Center" Color="Color.Primary">۹۹٫۹٪</MudText>
|
||||
<MudText Typo="Typo.caption" HtmlTag="div" Align="Align.Center" Color="Color.Info">آپتایم سرویس</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
<MudItem xs="6">
|
||||
<MudPaper Elevation="2" Class="pa-4 rounded-xl text-center">
|
||||
<MudText Typo="Typo.h4" Align="Align.Center" Color="Color.Warning">۳ روز</MudText>
|
||||
<MudText Typo="Typo.caption" HtmlTag="div" Align="Align.Center" Color="Color.Info">میانگین زمان استقرار</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
<MudItem xs="6">
|
||||
<MudPaper Elevation="2" Class="pa-4 rounded-xl text-center">
|
||||
<MudText Typo="Typo.h4" Align="Align.Center" Color="Color.Secondary">+۲۰ کشور</MudText>
|
||||
<MudText Typo="Typo.caption" HtmlTag="div" Align="Align.Center" Color="Color.Info">پوشش ارسال کد</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudStack>
|
||||
</MudItem>
|
||||
|
||||
</MudItem>
|
||||
}
|
||||
</MudGrid>
|
||||
|
||||
</MudContainer>
|
||||
</section>
|
||||
|
||||
<!-- TESTIMONIALS -->
|
||||
<section id="testimonials" class="py-20 testimonials">
|
||||
<MudContainer MaxWidth="MaxWidth.Large">
|
||||
<MudText Typo="Typo.h4" Align="Align.Center" GutterBottom="true">
|
||||
اعتماد مشتریان
|
||||
</MudText>
|
||||
<MudText Typo="Typo.subtitle1" Align="Align.Center" Class="mud-text-secondary mb-8">
|
||||
بخشی از تجربهٔ استفاده از «کارا بازار سلامت».
|
||||
</MudText>
|
||||
<MudGrid Spacing="3" Justify="Justify.Center">
|
||||
<MudItem xs="12" md="6">
|
||||
<MudPaper Elevation="4" Class=" pa-5 ">
|
||||
<MudStack Spacing="2">
|
||||
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
|
||||
<MudAvatar>
|
||||
<MudImage ObjectFit="ObjectFit.Cover"
|
||||
ObjectPosition="ObjectPosition.Center"
|
||||
Src="images/avatar1.jpg"></MudImage>
|
||||
</MudAvatar>
|
||||
<div>
|
||||
<MudText Typo="Typo.subtitle2">شرکت سینا نت</MudText>
|
||||
<MudText Typo="Typo.overline" Class="mud-text-secondary">مدیر عملیات</MudText>
|
||||
</div>
|
||||
</MudStack>
|
||||
<MudText Typo="Typo.body2">
|
||||
«با کارا بازار سلامت، محاسبهٔ کارمزدها و پایش تیمها بدون اکسل و دردسر انجام میشود.»
|
||||
</MudText>
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12" md="6">
|
||||
<MudPaper Elevation="4" Class=" pa-5 ">
|
||||
<MudStack Spacing="2">
|
||||
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
|
||||
<MudAvatar>
|
||||
<MudImage ObjectFit="ObjectFit.Cover"
|
||||
ObjectPosition="ObjectPosition.Center"
|
||||
Src="images/avatar2.jpg"></MudImage>
|
||||
</MudAvatar>
|
||||
<div>
|
||||
<MudText Typo="Typo.subtitle2">هولدینگ آریانا</MudText>
|
||||
<MudText Typo="Typo.overline" Class="mud-text-secondary">مدیر فروش</MudText>
|
||||
</div>
|
||||
</MudStack>
|
||||
<MudText Typo="Typo.body2">
|
||||
«شجرهنامهٔ بصری و گزارشهای دقیق باعث شد رشد تیم را لحظهای ببینیم.»
|
||||
</MudText>
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
|
||||
</MudGrid>
|
||||
</MudContainer>
|
||||
</section>
|
||||
|
||||
<!-- FAQ -->
|
||||
<section id="faq" class="mb-6">
|
||||
<MudContainer MaxWidth="MaxWidth.Large">
|
||||
<MudText Typo="Typo.h4" Align="Align.Center" GutterBottom="true">
|
||||
سوالات متداول
|
||||
</MudText>
|
||||
<MudText Typo="Typo.subtitle1" Align="Align.Center" Class="mud-text-secondary mb-8">
|
||||
پاسخ به سوالات رایج شما.
|
||||
</MudText>
|
||||
<div class="mx-auto max-w-800">
|
||||
<MudExpansionPanels Square="false" Class=" ">
|
||||
@foreach (var q in _faqs)
|
||||
{
|
||||
<MudExpansionPanel Text="@q.Q">
|
||||
<MudText Typo="Typo.body2">@q.A</MudText>
|
||||
</MudExpansionPanel>
|
||||
}
|
||||
</MudExpansionPanels>
|
||||
<div class="text-center mt-6">
|
||||
<MudButton Variant="Variant.Outlined" Color="Color.Primary" Class="rounded-pill"
|
||||
Href="/blog">
|
||||
مشاهده همه مقالات
|
||||
</MudButton>
|
||||
</div>
|
||||
</MudContainer>
|
||||
</section>
|
||||
}
|
||||
|
||||
</MudStack>
|
||||
@* ═══════════════════════════════════════════════
|
||||
6. TESTIMONIALS
|
||||
═══════════════════════════════════════════════ *@
|
||||
<section class="section-landing">
|
||||
<MudContainer MaxWidth="MaxWidth.Large">
|
||||
<div class="text-center mb-8 fade-in-up">
|
||||
<MudText Typo="Typo.h3">اعتماد مشتریان</MudText>
|
||||
<MudText Typo="Typo.body1" Class="mud-text-secondary mt-2">
|
||||
بخشی از تجربه استفاده از «کارا بازار سلامت»
|
||||
</MudText>
|
||||
</div>
|
||||
|
||||
<MudGrid Spacing="4" Justify="Justify.Center">
|
||||
@foreach (var t in _testimonials)
|
||||
{
|
||||
<MudItem xs="12" md="4">
|
||||
<MudPaper Elevation="2" Class="pa-5 rounded-xl testimonial-card fade-in-up" data-delay="@t.Delay">
|
||||
<MudStack Spacing="2">
|
||||
<MudRating ReadOnly="true" SelectedValue="5" Size="Size.Small" Color="Color.Warning" />
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary">
|
||||
«@t.Quote»
|
||||
</MudText>
|
||||
<MudDivider Class="my-1" />
|
||||
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
|
||||
<MudAvatar Size="Size.Small" Color="Color.Primary" Variant="Variant.Filled">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Person" Size="Size.Small" />
|
||||
</MudAvatar>
|
||||
<div>
|
||||
<MudText Typo="Typo.subtitle2">@t.Name</MudText>
|
||||
<MudText Typo="Typo.overline" Class="mud-text-secondary">@t.Role</MudText>
|
||||
</div>
|
||||
</MudStack>
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
}
|
||||
</MudGrid>
|
||||
</MudContainer>
|
||||
</section>
|
||||
|
||||
@* ═══════════════════════════════════════════════
|
||||
7. FAQ
|
||||
═══════════════════════════════════════════════ *@
|
||||
<section class="section-landing" style="background:var(--mud-palette-background-gray);">
|
||||
<MudContainer MaxWidth="MaxWidth.Medium">
|
||||
<div class="text-center mb-8 fade-in-up">
|
||||
<MudText Typo="Typo.h3">سوالات متداول</MudText>
|
||||
<MudText Typo="Typo.body1" Class="mud-text-secondary mt-2">
|
||||
پاسخ به سوالات رایج شما
|
||||
</MudText>
|
||||
</div>
|
||||
|
||||
<div class="fade-in-up">
|
||||
<MudExpansionPanels Elevation="1" Class="rounded-xl">
|
||||
@foreach (var q in _faqs)
|
||||
{
|
||||
<MudExpansionPanel Text="@q.Q">
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary">@q.A</MudText>
|
||||
</MudExpansionPanel>
|
||||
}
|
||||
</MudExpansionPanels>
|
||||
</div>
|
||||
</MudContainer>
|
||||
</section>
|
||||
|
||||
@* ═══════════════════════════════════════════════
|
||||
8. CTA BANNER
|
||||
═══════════════════════════════════════════════ *@
|
||||
<section class="section-landing-lg">
|
||||
<MudContainer MaxWidth="MaxWidth.Medium">
|
||||
<div class="cta-banner pa-8 pa-md-12 fade-in-up">
|
||||
<MudStack AlignItems="AlignItems.Center" Spacing="3">
|
||||
<MudText Typo="Typo.h3" Align="Align.Center" Class="dash-hero-name">
|
||||
آماده شروع هستید؟
|
||||
</MudText>
|
||||
<MudText Typo="Typo.body1" Align="Align.Center" Class="hero-subtitle">
|
||||
همین الان ثبتنام کنید و از مزایای کارا بازار سلامت بهرهمند شوید.
|
||||
</MudText>
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Size="Size.Large"
|
||||
Class="rounded-pill mt-2 hero-cta-primary"
|
||||
OnClick="NavigateToRegistrationWizard">
|
||||
شروع رایگان
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
</div>
|
||||
</MudContainer>
|
||||
</section>
|
||||
|
||||
@@ -1,44 +1,66 @@
|
||||
using FrontOffice.BFF.Package.Protobuf.Protos.Package;
|
||||
using FrontOffice.Main.Utilities;
|
||||
using FrontOffice.Main.Utilities;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.JSInterop;
|
||||
using MudBlazor;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace FrontOffice.Main.Pages;
|
||||
|
||||
public partial class Index:IDisposable
|
||||
public partial class Index : IDisposable
|
||||
{
|
||||
[Inject] private PackageContract.PackageContractClient PackageClient { get; set; } = default!;
|
||||
[Inject] private MobileNumberEncryptor Encryptor { get; set; }
|
||||
private string? _email;
|
||||
private bool _isLoadingPackages;
|
||||
private List<Pack> _packs = new();
|
||||
[Inject] private BlogPostService BlogPostService { get; set; } = default!;
|
||||
[Inject] private AuthService AuthService { get; set; } = default!;
|
||||
|
||||
// ── Latest blog posts (loaded from CMS) ──
|
||||
private List<BlogPostCardDto> _latestPosts = new();
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
MainService.OnChangeHandler +=async () =>
|
||||
MainService.OnChangeHandler += OnStateChanged;
|
||||
|
||||
// Load latest published blog posts
|
||||
// اول پینشدهها، اگر کافی نبود آخرین پستها اضافه شود
|
||||
try
|
||||
{
|
||||
await InvokeAsync(StateHasChanged);
|
||||
};
|
||||
// await LoadPackagesAsync();
|
||||
|
||||
//string mobileNumber = "09387342688";
|
||||
|
||||
//// انکریپت کردن
|
||||
//string encrypted = Encryptor.EncryptMobileNumber(mobileNumber);
|
||||
//Console.WriteLine($"Encrypted: {encrypted}");
|
||||
|
||||
//// دیکریپت کردن برای تست
|
||||
//string decrypted = Encryptor.DecryptMobileNumber(encrypted);
|
||||
//Console.WriteLine($"Decrypted: {decrypted}");
|
||||
_latestPosts = await BlogPostService.GetFeaturedPostsAsync(2);
|
||||
|
||||
// اگر فقط ۱ پینشده داریم، ۱ پست آخر هم اضافه کن
|
||||
if (_latestPosts.Count < 2)
|
||||
{
|
||||
var result = await BlogPostService.GetPublishedPostsAsync(page: 1, pageSize: 2);
|
||||
var existing = _latestPosts.Select(p => p.Id).ToHashSet();
|
||||
foreach (var post in result.Posts)
|
||||
{
|
||||
if (!existing.Contains(post.Id))
|
||||
{
|
||||
_latestPosts.Add(post);
|
||||
if (_latestPosts.Count >= 2) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
_latestPosts = new();
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
// Init scroll-triggered fade-in animations
|
||||
await JS.InvokeVoidAsync("initScrollAnimations");
|
||||
|
||||
// Animate stat counters
|
||||
foreach (var stat in _stats)
|
||||
{
|
||||
await JS.InvokeVoidAsync("animateCounter", stat.ElementId, stat.Target, 2200, stat.Suffix);
|
||||
}
|
||||
}
|
||||
|
||||
if (await AuthService.IsAuthenticatedAsync())
|
||||
{
|
||||
if ((await AuthService.IsCompleteRegisterAsync()))
|
||||
if (await AuthService.IsCompleteRegisterAsync())
|
||||
{
|
||||
Navigation.NavigateTo(RouteConstants.Profile.Index);
|
||||
}
|
||||
@@ -47,56 +69,8 @@ public partial class Index:IDisposable
|
||||
Navigation.NavigateTo(RouteConstants.Registration.Wizard);
|
||||
}
|
||||
}
|
||||
await base.OnAfterRenderAsync(firstRender);
|
||||
}
|
||||
|
||||
// private async Task LoadPackagesAsync()
|
||||
// {
|
||||
// _isLoadingPackages = true;
|
||||
// try
|
||||
// {
|
||||
// var response = await PackageClient.GetAllPackageByFilterAsync(request: new());
|
||||
// if (response?.Models?.Any() == true)
|
||||
// {
|
||||
// _packs = response.Models.Select(p => new Pack(
|
||||
// Id: p.Id,
|
||||
// Title: p.Title,
|
||||
// Body: p.Description,
|
||||
// Image: UrlUtility.DownloadUrl + p.ImagePath,
|
||||
// Price: p.Price
|
||||
// )).ToList();
|
||||
// }
|
||||
// else
|
||||
// _packs = new List<Pack>();
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// Snackbar.Add($"خطا در بارگذاری پکیجها: {ex.Message}", Severity.Error);
|
||||
// // Fallback to empty list
|
||||
// _packs = new List<Pack>();
|
||||
// }
|
||||
// finally
|
||||
// {
|
||||
// _isLoadingPackages = false;
|
||||
// await InvokeAsync(StateHasChanged);
|
||||
// }
|
||||
// }
|
||||
|
||||
private void JoinWaitlist()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_email))
|
||||
{
|
||||
Snackbar.Add("لطفاً ایمیل معتبر وارد کنید.", Severity.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
Snackbar.Add("به لیست انتظار «کارا بازار سلامت» اضافه شدید.", Severity.Success);
|
||||
_email = string.Empty;
|
||||
}
|
||||
|
||||
private void NavigateToPackage(long packageId)
|
||||
{
|
||||
Navigation.NavigateTo($"{RouteConstants.Package.Detail}{packageId}");
|
||||
await base.OnAfterRenderAsync(firstRender);
|
||||
}
|
||||
|
||||
private void NavigateToRegistrationWizard()
|
||||
@@ -104,25 +78,77 @@ public partial class Index:IDisposable
|
||||
Navigation.NavigateTo(RouteConstants.Registration.Wizard);
|
||||
}
|
||||
|
||||
private record Pack(long Id, string Title, string Body, string Image, long Price);
|
||||
private void NavigateToPost(string slug)
|
||||
{
|
||||
Navigation.NavigateTo($"/blog/{slug}");
|
||||
}
|
||||
|
||||
private record Plan(string Name, string Price, bool Highlight, IEnumerable<string> Features);
|
||||
private async void OnStateChanged()
|
||||
{
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
private record QA(string Q, string A);
|
||||
// ── Trust badges ──
|
||||
private readonly List<(string Icon, string Text)> _trustBadges = new()
|
||||
{
|
||||
(Icons.Material.Outlined.Timer, "ثبتنام زیر ۲ دقیقه"),
|
||||
(Icons.Material.Outlined.SupportAgent, "پشتیبانی ۷×۲۴"),
|
||||
(Icons.Material.Outlined.Lock, "پرداخت ایمن"),
|
||||
(Icons.Material.Outlined.Verified, "تضمین کیفیت"),
|
||||
};
|
||||
|
||||
// ── How it works steps ──
|
||||
private readonly List<(string Title, string Desc)> _steps = new()
|
||||
{
|
||||
("ثبتنام و احراز هویت", "یک حساب بسازید، شماره موبایل را تأیید و اطلاعات هویتی را تکمیل کنید."),
|
||||
("دعوت دوستان", "لینک دعوت اختصاصی خود را با دوستان و آشنایان به اشتراک بگذارید."),
|
||||
("دریافت پاداش", "از خریدهای واقعی اعضای تیمتان پاداش شفاف و لحظهای دریافت کنید."),
|
||||
};
|
||||
|
||||
// ── Feature cards data (6 cards) ──
|
||||
private readonly List<(string Icon, string Title, string Desc, int Delay)> _features = new()
|
||||
{
|
||||
(Icons.Material.Outlined.VerifiedUser, "ثبتنام سریع و ساده", "در چند گام کوتاه حساب بسازید و شروع کنید.", 0),
|
||||
(Icons.Material.Outlined.StarBorder, "پاداشهای شفاف", "قوانین روشن، دسترسی آسان به سوابق و گزارشها.", 100),
|
||||
(Icons.Material.Outlined.Devices, "طراحی واکنشگرا", "تجربهای روان در موبایل و دسکتاپ.", 200),
|
||||
(Icons.Material.Outlined.Groups, "تیمسازی هوشمند", "ساختار درختی شبکه و مدیریت تیمهای فروش.", 300),
|
||||
(Icons.Material.Outlined.Insights, "گزارشهای لحظهای", "داشبورد پویا برای مشاهده عملکرد و کمیسیون.", 400),
|
||||
(Icons.Material.Outlined.Lock, "امنیت بالا", "رمزنگاری اطلاعات و احراز هویت چندمرحلهای.", 500),
|
||||
};
|
||||
|
||||
// ── Stats data (animated counters) ──
|
||||
private readonly List<StatItem> _stats = new()
|
||||
{
|
||||
new("stat-growth", "+۵۰٪", 50, "%+", Color.Success, "رشد میانگین تیم"),
|
||||
new("stat-uptime", "۹۹.۹٪", 99.9, "%", Color.Primary, "آپتایم سرویس"),
|
||||
new("stat-deploy", "۳ روز", 3, " روز", Color.Warning, "میانگین زمان استقرار"),
|
||||
new("stat-coverage", "+۲۰ کشور", 20, "+", Color.Secondary, "پوشش ارسال کد"),
|
||||
};
|
||||
|
||||
// ── Testimonials ──
|
||||
private readonly List<TestimonialItem> _testimonials = new()
|
||||
{
|
||||
new("با کارا بازار سلامت، محاسبه کارمزدها و پایش تیمها بدون اکسل و دردسر انجام میشود.", "شرکت سینا نت", "مدیر عملیات", 0),
|
||||
new("شجرهنامه بصری و گزارشهای دقیق باعث شد رشد تیم را لحظهای ببینیم.", "هولدینگ آریانا", "مدیر فروش", 150),
|
||||
new("سادگی ثبتنام و شفافیت پاداشها مهمترین مزیت این پلتفرم است.", "گروه بهداشتی نوین", "مدیر توسعه", 300),
|
||||
};
|
||||
|
||||
// ── FAQ data ──
|
||||
private readonly List<QA> _faqs = new()
|
||||
{
|
||||
new("دامنهٔ اختصاصی دارم؛ قابل اتصال است؟", "بله، پشت دامنه و گواهی SSL خودتان مستقر میشود."),
|
||||
new("دامنه اختصاصی دارم؛ قابل اتصال است؟", "بله، پشت دامنه و گواهی SSL خودتان مستقر میشود."),
|
||||
new("با دیتابیس خودم کار میکند؟", "کاملاً. SQL Server، PostgreSQL و MySQL پشتیبانی میشود."),
|
||||
new("چه درگاههایی پشتیبانی میشود؟", "Stripe و PayPal یا درگاه اختصاصی از طریق وبهوکها."),
|
||||
new("میتوانم دادهها را خروجی بگیرم؟", "هر زمان از داشبورد ادمین خروجی CSV/Excel بگیرید."),
|
||||
};
|
||||
|
||||
// ── Records ──
|
||||
private record QA(string Q, string A);
|
||||
private record StatItem(string ElementId, string Display, double Target, string Suffix, Color Color, string Label);
|
||||
private record TestimonialItem(string Quote, string Name, string Role, int Delay);
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
MainService.OnChangeHandler -=async () =>
|
||||
{
|
||||
await InvokeAsync(StateHasChanged);
|
||||
};
|
||||
MainService.OnChangeHandler -= OnStateChanged;
|
||||
}
|
||||
}
|
||||
@@ -6,14 +6,11 @@
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
||||
<MudStack Spacing="3">
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
||||
<MudText Typo="Typo.h5">آمار باشگاه توسعه دهندگان</MudText>
|
||||
<MudButton Variant="Variant.Text" StartIcon="@Icons.Material.Filled.ArrowBack" Href="@RouteConstants.Profile.Index">بازگشت</MudButton>
|
||||
</MudStack>
|
||||
<PageHeader Title="آمار باشگاه توسعه دهندگان" BackHref="@RouteConstants.Profile.Index" />
|
||||
|
||||
@if (_isLoading)
|
||||
{
|
||||
<MudProgressLinear Color="Color.Primary" Indeterminate="true" />
|
||||
<LoadingState Message="در حال دریافت آمار..." />
|
||||
}
|
||||
else if (_statistics is not null)
|
||||
{
|
||||
|
||||
@@ -3,16 +3,7 @@
|
||||
<PageTitle>پکیجهای من</PageTitle>
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.Large" Class="py-8">
|
||||
<!-- Breadcrumb -->
|
||||
<MudBreadcrumbs Items="_breadcrumbItems" Class="mb-4" />
|
||||
|
||||
<!-- Header -->
|
||||
<MudStack Class="mb-6">
|
||||
<MudText Typo="Typo.h4">پکیجهای من</MudText>
|
||||
<MudText Typo="Typo.body1" Class="mud-text-secondary">
|
||||
وضعیت پکیجهای خریداری شده و عضویت باشگاه مشتریان
|
||||
</MudText>
|
||||
</MudStack>
|
||||
<PageHeader Title="پکیجهای من" BackHref="@RouteConstants.Profile.Index" />
|
||||
|
||||
@if (_isLoading)
|
||||
{
|
||||
|
||||
@@ -3,13 +3,7 @@
|
||||
<PageTitle>پکیجها</PageTitle>
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.Large" Class="py-8">
|
||||
<!-- Header -->
|
||||
<MudStack Class="mb-6">
|
||||
<MudText Typo="Typo.h4">پکیجهای سرمایهگذاری</MudText>
|
||||
<MudText Typo="Typo.body1" Class="mud-text-secondary">
|
||||
با خرید پکیج طلایی، به باشگاه مشتریان بپیوندید و از مزایای ویژه بهرهمند شوید.
|
||||
</MudText>
|
||||
</MudStack>
|
||||
<PageHeader Title="پکیجهای سرمایهگذاری" BackHref="@RouteConstants.Profile.Index" />
|
||||
|
||||
@if (_isLoading)
|
||||
{
|
||||
|
||||
@@ -4,26 +4,25 @@
|
||||
|
||||
@if (_isLoading)
|
||||
{
|
||||
<MudStack AlignItems="AlignItems.Center" Class="py-16">
|
||||
<MudProgressCircular Color="Color.Primary" Indeterminate="true" Size="Size.Large" />
|
||||
<MudText Typo="Typo.body1" Class="mud-text-secondary mt-2">در حال بارگذاری...</MudText>
|
||||
</MudStack>
|
||||
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
||||
<LoadingState Message="در حال بارگذاری..." />
|
||||
</MudContainer>
|
||||
}
|
||||
else if (_package == null)
|
||||
{
|
||||
<MudStack AlignItems="AlignItems.Center" Class="py-16">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Error" Size="Size.Large" Color="Color.Error" />
|
||||
<MudText Typo="Typo.h5" Class="mt-2">پکیج یافت نشد</MudText>
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary">پکیج مورد نظر وجود ندارد یا حذف شده است.</MudText>
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" Class="mt-4" OnClick="() => Navigation.NavigateTo(RouteConstants.Main.MainPage)">
|
||||
بازگشت به صفحه اصلی
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
||||
<EmptyState Icon="@Icons.Material.Filled.Error"
|
||||
Title="پکیج یافت نشد"
|
||||
Description="پکیج مورد نظر وجود ندارد یا حذف شده است."
|
||||
ActionText="بازگشت به صفحه اصلی"
|
||||
ActionHref="@RouteConstants.Main.MainPage" />
|
||||
</MudContainer>
|
||||
}
|
||||
else
|
||||
{
|
||||
<!-- Breadcrumb -->
|
||||
<MudContainer MaxWidth="MaxWidth.Large" Class="py-4">
|
||||
<PageHeader Title="جزئیات پکیج" BackHref="@RouteConstants.Package.List" />
|
||||
<MudBreadcrumbs Items="_breadcrumbItems" />
|
||||
</MudContainer>
|
||||
|
||||
@@ -36,9 +35,9 @@ else
|
||||
<MudStack Class="mb-4">
|
||||
<!-- Package Image -->
|
||||
<MudPaper Class="pa-2 rounded-xl" Style="background: radial-gradient(600px 280px at 120% 0, #daccff 0, transparent 60%), radial-gradient(600px 280px at -10% 100%, #ffe2f2 0, transparent 60%), linear-gradient(180deg, #fff, #fbfaff);">
|
||||
<MudImage Src="@_package.Image"
|
||||
<AppImage Path="@_package.Image"
|
||||
Alt="@_package.Title"
|
||||
Height="250"
|
||||
ImgHeight="250"
|
||||
ObjectFit="ObjectFit.Cover"
|
||||
ObjectPosition="ObjectPosition.Center"
|
||||
Style="width:100%"
|
||||
@@ -228,9 +227,9 @@ else
|
||||
<MudItem xs="12" md="4">
|
||||
<MudPaper Elevation="2" Class="pa-4 d-flex flex-column h-100 cursor-pointer"
|
||||
OnClick="() => NavigateToPackage(relatedPackage.Id)">
|
||||
<MudImage Src="@relatedPackage.Image"
|
||||
<AppImage Path="@relatedPackage.Image"
|
||||
Alt="@relatedPackage.Title"
|
||||
Height="200"
|
||||
ImgHeight="200"
|
||||
ObjectFit="ObjectFit.Cover"
|
||||
Class="rounded-xl mb-3" />
|
||||
<MudText Typo="Typo.h6" Class="mb-2">@relatedPackage.Title</MudText>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using Blazored.LocalStorage;
|
||||
using FrontOffice.BFF.Package.Protobuf.Protos.Package;
|
||||
using CMSMicroservice.Protobuf.Protos.Package;
|
||||
using FrontOffice.Main.Shared;
|
||||
using FrontOffice.Main.Utilities;
|
||||
using Grpc.Core;
|
||||
@@ -74,7 +74,7 @@ public partial class PackageDetail : IDisposable
|
||||
Id = packageResponse.Id,
|
||||
Title = packageResponse.Title,
|
||||
Body = packageResponse.Description,
|
||||
Image = UrlUtility.DownloadUrl + packageResponse.ImagePath,
|
||||
Image = packageResponse.ImagePath ?? string.Empty,
|
||||
Specifications = new List<Specification>
|
||||
{
|
||||
new() { Name = "ظرفیت", Value = "تا ۲۰۰ عضو", Icon = Icons.Material.Filled.Group },
|
||||
|
||||
@@ -4,10 +4,7 @@
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
||||
<MudStack Spacing="3">
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
||||
<MudText Typo="Typo.h5">مدیریت آدرسها</MudText>
|
||||
<MudButton Variant="Variant.Text" StartIcon="@Icons.Material.Filled.ArrowBack" Href="@RouteConstants.Profile.Index">بازگشت</MudButton>
|
||||
</MudStack>
|
||||
<PageHeader Title="مدیریت آدرسها" BackHref="@RouteConstants.Profile.Index" />
|
||||
|
||||
<MudPaper Elevation="2" Class="pa-4 rounded-lg">
|
||||
<MudStack Spacing="4">
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using FrontOffice.BFF.UserAddress.Protobuf.Protos.UserAddress;
|
||||
using CMSMicroservice.Protobuf.Protos.UserAddress;
|
||||
using CMSMicroservice.Protobuf.Protos.UserAddress;
|
||||
using FrontOffice.Main.Pages.Profile.Components;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
@@ -9,7 +10,7 @@ public partial class Addresses : ComponentBase
|
||||
{
|
||||
[Inject] private UserAddressContract.UserAddressContractClient UserAddressContract { get; set; } = default!;
|
||||
|
||||
private List<GetAllUserAddressByFilterResponseModel> _addresses = new();
|
||||
private List<CustomerAddressModel> _addresses = new();
|
||||
private bool _isLoadingAddresses;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
@@ -22,7 +23,7 @@ public partial class Addresses : ComponentBase
|
||||
_isLoadingAddresses = true;
|
||||
try
|
||||
{
|
||||
var response = await UserAddressContract.GetAllUserAddressByFilterAsync(new());
|
||||
var response = await UserAddressContract.GetCustomerAddressesAsync(new());
|
||||
_addresses = response?.Models?.ToList() ?? new();
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -44,7 +45,7 @@ public partial class Addresses : ComponentBase
|
||||
await LoadAddresses();
|
||||
}
|
||||
|
||||
private async Task OpenEditAddressDialog(GetAllUserAddressByFilterResponseModel address)
|
||||
private async Task OpenEditAddressDialog(CustomerAddressModel address)
|
||||
{
|
||||
var dialog = await DialogService.ShowAsync<EditAddressDialog>("ویرایش آدرس", new DialogParameters<EditAddressDialog>
|
||||
{
|
||||
@@ -59,7 +60,7 @@ public partial class Addresses : ComponentBase
|
||||
{
|
||||
try
|
||||
{
|
||||
await UserAddressContract.SetAddressAsDefaultAsync(new() { Id = id });
|
||||
await UserAddressContract.SetCustomerDefaultAddressAsync(new() { Id = id });
|
||||
Snackbar.Add("آدرس پیشفرض تغییر کرد.", Severity.Success);
|
||||
await LoadAddresses();
|
||||
}
|
||||
@@ -76,7 +77,7 @@ public partial class Addresses : ComponentBase
|
||||
{
|
||||
try
|
||||
{
|
||||
await UserAddressContract.DeleteUserAddressAsync(new() { Id = id });
|
||||
await UserAddressContract.DeleteCustomerAddressAsync(new() { Id = id });
|
||||
Snackbar.Add("آدرس حذف شد.", Severity.Success);
|
||||
await LoadAddresses();
|
||||
}
|
||||
|
||||
@@ -4,10 +4,7 @@
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.Small" Class="py-6">
|
||||
<MudStack Spacing="3">
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
||||
<MudText Typo="Typo.h5">تغییر رمز عبور</MudText>
|
||||
<MudButton Variant="Variant.Text" StartIcon="@Icons.Material.Filled.ArrowBack" Href="@RouteConstants.Profile.Settings">بازگشت</MudButton>
|
||||
</MudStack>
|
||||
<PageHeader Title="تغییر رمز عبور" BackHref="@RouteConstants.Profile.Settings" />
|
||||
|
||||
<MudPaper Elevation="2" Class="pa-4 rounded-lg">
|
||||
<MudStack Spacing="3">
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
|
||||
<MudDialog >
|
||||
@using CMSMicroservice.Protobuf.Protos.City
|
||||
<MudDialog>
|
||||
<TitleContent>
|
||||
<MudText Typo="Typo.h4" Align="Align.Center">افزودن آدرس جدید</MudText>
|
||||
</TitleContent>
|
||||
<DialogContent>
|
||||
<MudForm @ref="_form" Model="_request" Validation="@(_validator.ValidateValue)">
|
||||
<MudForm @ref="_form" Model="_request">
|
||||
<MudStack Spacing="3">
|
||||
<MudTextField @bind-Value="_request.Title"
|
||||
For="@(() => _request.Title)"
|
||||
@@ -29,12 +30,12 @@
|
||||
Required="true"
|
||||
RequiredError="کد پستی الزامی است." />
|
||||
|
||||
<MudAutocomplete T="CityProto.GetAllCitiesByFilterResponseModel"
|
||||
<MudAutocomplete T="object"
|
||||
@bind-Value="_selectedCity"
|
||||
Label="شهر"
|
||||
Variant="Variant.Outlined"
|
||||
SearchFunc="SearchCities"
|
||||
ToStringFunc="@(city => city != null ? $"{city.Native} ({city.StateName})" : string.Empty)"
|
||||
ToStringFunc="@(city => city != null ? $"{((CMSMicroservice.Protobuf.Protos.City.CityDto)city).Native} ({((CMSMicroservice.Protobuf.Protos.City.CityDto)city).StateName})" : string.Empty)"
|
||||
Required="true"
|
||||
RequiredError="شهر الزامی است."
|
||||
Clearable="true"
|
||||
@@ -49,8 +50,8 @@
|
||||
<ItemTemplate Context="city">
|
||||
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
|
||||
<MudIcon Icon="@Icons.Material.Filled.LocationCity" Size="Size.Small" />
|
||||
<MudText>@city.Native</MudText>
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">(@city.StateName)</MudText>
|
||||
<MudText>@(((CMSMicroservice.Protobuf.Protos.City.CityDto)city).Native)</MudText>
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">(@(((CMSMicroservice.Protobuf.Protos.City.CityDto)city).StateName))</MudText>
|
||||
</MudStack>
|
||||
</ItemTemplate>
|
||||
<NoItemsTemplate>
|
||||
|
||||
@@ -1,43 +1,42 @@
|
||||
using FrontOffice.BFF.UserAddress.Protobuf.Protos.UserAddress;
|
||||
using FrontOffice.BFF.UserAddress.Protobuf.Validator;
|
||||
using CMSMicroservice.Protobuf.Protos.UserAddress;
|
||||
using CMSMicroservice.Protobuf.Protos.City;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
using Severity = MudBlazor.Severity;
|
||||
using CityProto = FrontOffice.BFF.City.Protobuf;
|
||||
|
||||
namespace FrontOffice.Main.Pages.Profile.Components;
|
||||
|
||||
public record CityModel(long Id, string Name);
|
||||
|
||||
public partial class AddAddressDialog : ComponentBase
|
||||
{
|
||||
[CascadingParameter] private IMudDialogInstance MudDialog { get; set; } = default!;
|
||||
[Inject] private UserAddressContract.UserAddressContractClient UserAddressContract { get; set; } = default!;
|
||||
[Inject] private CityProto.CityContract.CityContractClient CityContract { get; set; } = default!;
|
||||
[Inject] private CityContract.CityContractClient CityContract { get; set; } = default!;
|
||||
|
||||
private MudForm? _form;
|
||||
private readonly CreateNewUserAddressRequestValidator _validator = new();
|
||||
private bool _isSaving;
|
||||
private CreateNewUserAddressRequest _request = new();
|
||||
private CityProto.GetAllCitiesByFilterResponseModel? _selectedCity;
|
||||
private CreateCustomerAddressRequest _request = new();
|
||||
private object? _selectedCity;
|
||||
|
||||
private async Task<IEnumerable<CityProto.GetAllCitiesByFilterResponseModel>> SearchCities(string value,
|
||||
CancellationToken ct)
|
||||
private async Task<IEnumerable<object>> SearchCities(string value, CancellationToken ct)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value) || value.Length < 2)
|
||||
return Enumerable.Empty<CityProto.GetAllCitiesByFilterResponseModel>();
|
||||
return Enumerable.Empty<object>();
|
||||
|
||||
try
|
||||
{
|
||||
var response = await CityContract.GetAllCitiesByFilterAsync(new CityProto.GetAllCitiesByFilterRequest
|
||||
var response = await CityContract.GetAllCitiesByFilterAsync(new GetAllCitiesByFilterRequest
|
||||
{
|
||||
PaginationState = new CityProto.PaginationState { PageNumber = 1, PageSize = 20 },
|
||||
Filter = new CityProto.GetAllCitiesByFilterFilter { Name = value }
|
||||
}, cancellationToken: ct);
|
||||
|
||||
return response?.Models?.ToList() ?? new List<CityProto.GetAllCitiesByFilterResponseModel>();
|
||||
PaginationState = new CMSMicroservice.Protobuf.Protos.City.PaginationState { PageNumber = 1, PageSize = 20 },
|
||||
Filter = new GetAllCitiesByFilterFilter { Name = value }
|
||||
});
|
||||
|
||||
return response?.Cities?.Cast<object>() ?? Enumerable.Empty<object>();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return Enumerable.Empty<CityProto.GetAllCitiesByFilterResponseModel>();
|
||||
return Enumerable.Empty<object>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,11 +52,11 @@ public partial class AddAddressDialog : ComponentBase
|
||||
return;
|
||||
}
|
||||
|
||||
_request.CityId = _selectedCity.Id;
|
||||
_request.CityId = (long)((dynamic)_selectedCity).Id;
|
||||
_isSaving = true;
|
||||
try
|
||||
{
|
||||
var response = await UserAddressContract.CreateNewUserAddressAsync(_request);
|
||||
var response = await UserAddressContract.CreateCustomerAddressAsync(_request);
|
||||
Snackbar.Add("آدرس با موفقیت اضافه شد.", Severity.Success);
|
||||
MudDialog.Close(DialogResult.Ok(true));
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
@using CMSMicroservice.Protobuf.Protos.City
|
||||
<MudDialog>
|
||||
<TitleContent>
|
||||
<MudText Typo="Typo.h4" Align="Align.Center">ویرایش آدرس</MudText>
|
||||
</TitleContent>
|
||||
<DialogContent>
|
||||
<MudForm @ref="_form" Model="_request" Validation="@(_validator.ValidateValue)">
|
||||
<MudForm @ref="_form" Model="_request">
|
||||
<MudStack Spacing="3">
|
||||
<MudTextField @bind-Value="_request.Title"
|
||||
For="@(() => _request.Title)"
|
||||
@@ -28,12 +29,12 @@
|
||||
Required="true"
|
||||
RequiredError="کد پستی الزامی است." />
|
||||
|
||||
<MudAutocomplete T="CityProto.GetAllCitiesByFilterResponseModel"
|
||||
<MudAutocomplete T="object"
|
||||
@bind-Value="_selectedCity"
|
||||
Label="شهر"
|
||||
Variant="Variant.Outlined"
|
||||
SearchFunc="SearchCities"
|
||||
ToStringFunc="@(city => city != null ? $"{city.Native} ({city.StateName})" : string.Empty)"
|
||||
ToStringFunc="@(city => city != null ? $"{((CMSMicroservice.Protobuf.Protos.City.CityDto)city).Native} ({((CMSMicroservice.Protobuf.Protos.City.CityDto)city).StateName})" : string.Empty)"
|
||||
Required="true"
|
||||
RequiredError="شهر الزامی است."
|
||||
Clearable="true"
|
||||
@@ -48,8 +49,8 @@
|
||||
<ItemTemplate Context="city">
|
||||
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
|
||||
<MudIcon Icon="@Icons.Material.Filled.LocationCity" Size="Size.Small" />
|
||||
<MudText>@city.Native</MudText>
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">(@city.StateName)</MudText>
|
||||
<MudText>@(((CMSMicroservice.Protobuf.Protos.City.CityDto)city).Native)</MudText>
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">(@(((CMSMicroservice.Protobuf.Protos.City.CityDto)city).StateName))</MudText>
|
||||
</MudStack>
|
||||
</ItemTemplate>
|
||||
<NoItemsTemplate>
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
using FrontOffice.BFF.UserAddress.Protobuf.Protos.UserAddress;
|
||||
using FrontOffice.BFF.UserAddress.Protobuf.Validator;
|
||||
using CMSMicroservice.Protobuf.Protos.UserAddress;
|
||||
using CMSMicroservice.Protobuf.Protos.City;
|
||||
using Mapster;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
using Severity = MudBlazor.Severity;
|
||||
using CityProto = FrontOffice.BFF.City.Protobuf;
|
||||
|
||||
namespace FrontOffice.Main.Pages.Profile.Components;
|
||||
|
||||
@@ -12,22 +11,21 @@ public partial class EditAddressDialog : ComponentBase
|
||||
{
|
||||
[CascadingParameter] private IMudDialogInstance MudDialog { get; set; } = default!;
|
||||
[Inject] private UserAddressContract.UserAddressContractClient UserAddressContract { get; set; } = default!;
|
||||
[Inject] private CityProto.CityContract.CityContractClient CityContract { get; set; } = default!;
|
||||
[Inject] private CityContract.CityContractClient CityContract { get; set; } = default!;
|
||||
|
||||
[Parameter] public GetAllUserAddressByFilterResponseModel? Model { get; set; }
|
||||
[Parameter] public CustomerAddressModel? Model { get; set; }
|
||||
|
||||
private MudForm? _form;
|
||||
private readonly UpdateUserAddressRequestValidator _validator = new();
|
||||
private bool _isSaving;
|
||||
private UpdateUserAddressRequest _request = new();
|
||||
private CityProto.GetAllCitiesByFilterResponseModel? _selectedCity;
|
||||
private UpdateCustomerAddressRequest _request = new();
|
||||
private object? _selectedCity;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await base.OnInitializedAsync();
|
||||
if (Model != null)
|
||||
{
|
||||
_request = Model.Adapt<UpdateUserAddressRequest>();
|
||||
_request = Model.Adapt<UpdateCustomerAddressRequest>();
|
||||
|
||||
// Load selected city if CityId exists
|
||||
if (Model.CityId > 0)
|
||||
@@ -41,13 +39,13 @@ public partial class EditAddressDialog : ComponentBase
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await CityContract.GetAllCitiesByFilterAsync(new CityProto.GetAllCitiesByFilterRequest
|
||||
var response = await CityContract.GetAllCitiesByFilterAsync(new GetAllCitiesByFilterRequest
|
||||
{
|
||||
PaginationState = new CityProto.PaginationState { PageNumber = 1, PageSize = 1 },
|
||||
Filter = new CityProto.GetAllCitiesByFilterFilter { Id = cityId }
|
||||
PaginationState = new CMSMicroservice.Protobuf.Protos.City.PaginationState { PageNumber = 1, PageSize = 1 },
|
||||
Filter = new GetAllCitiesByFilterFilter { Id = cityId }
|
||||
});
|
||||
|
||||
_selectedCity = response?.Models?.FirstOrDefault();
|
||||
_selectedCity = response?.Cities?.FirstOrDefault();
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -55,24 +53,24 @@ public partial class EditAddressDialog : ComponentBase
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<IEnumerable<CityProto.GetAllCitiesByFilterResponseModel>> SearchCities(string value, CancellationToken ct)
|
||||
private async Task<IEnumerable<object>> SearchCities(string value, CancellationToken ct)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value) || value.Length < 2)
|
||||
return Enumerable.Empty<CityProto.GetAllCitiesByFilterResponseModel>();
|
||||
return Enumerable.Empty<object>();
|
||||
|
||||
try
|
||||
{
|
||||
var response = await CityContract.GetAllCitiesByFilterAsync(new CityProto.GetAllCitiesByFilterRequest
|
||||
var response = await CityContract.GetAllCitiesByFilterAsync(new GetAllCitiesByFilterRequest
|
||||
{
|
||||
PaginationState = new CityProto.PaginationState { PageNumber = 1, PageSize = 20 },
|
||||
Filter = new CityProto.GetAllCitiesByFilterFilter { Native = value }
|
||||
}, cancellationToken: ct);
|
||||
|
||||
return response?.Models?.ToList() ?? new List<CityProto.GetAllCitiesByFilterResponseModel>();
|
||||
PaginationState = new CMSMicroservice.Protobuf.Protos.City.PaginationState { PageNumber = 1, PageSize = 20 },
|
||||
Filter = new GetAllCitiesByFilterFilter { Name = value }
|
||||
});
|
||||
|
||||
return response?.Cities?.Cast<object>() ?? Enumerable.Empty<object>();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return Enumerable.Empty<CityProto.GetAllCitiesByFilterResponseModel>();
|
||||
return Enumerable.Empty<object>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,11 +86,11 @@ public partial class EditAddressDialog : ComponentBase
|
||||
return;
|
||||
}
|
||||
|
||||
_request.CityId = _selectedCity.Id;
|
||||
_request.CityId = (long)((dynamic)_selectedCity).Id;
|
||||
_isSaving = true;
|
||||
try
|
||||
{
|
||||
await UserAddressContract.UpdateUserAddressAsync(_request);
|
||||
await UserAddressContract.UpdateCustomerAddressAsync(_request);
|
||||
Snackbar.Add("آدرس با موفقیت ویرایش شد.", Severity.Success);
|
||||
MudDialog.Close(DialogResult.Ok(true));
|
||||
}
|
||||
|
||||
@@ -7,12 +7,12 @@
|
||||
|
||||
<MudSelect T="int" @bind-Value="_selectedDepth" Label="عمق" Variant="Variant.Outlined"
|
||||
Dense="true" Margin="Margin.Dense" Style="width: 90px; min-width: 90px;">
|
||||
<MudSelectItem Value="2">2 سطح</MudSelectItem>
|
||||
<MudSelectItem Value="3">3 سطح</MudSelectItem>
|
||||
<MudSelectItem Value="4">4 سطح</MudSelectItem>
|
||||
<MudSelectItem Value="5">5 سطح</MudSelectItem>
|
||||
<MudSelectItem Value="6">6 سطح</MudSelectItem>
|
||||
<MudSelectItem Value="15">همه</MudSelectItem>
|
||||
<MudSelectItem T="int" Value="2">2 سطح</MudSelectItem>
|
||||
<MudSelectItem T="int" Value="3">3 سطح</MudSelectItem>
|
||||
<MudSelectItem T="int" Value="4">4 سطح</MudSelectItem>
|
||||
<MudSelectItem T="int" Value="5">5 سطح</MudSelectItem>
|
||||
<MudSelectItem T="int" Value="6">6 سطح</MudSelectItem>
|
||||
<MudSelectItem T="int" Value="15">همه</MudSelectItem>
|
||||
</MudSelect>
|
||||
|
||||
<MudButtonGroup Variant="Variant.Outlined" Size="Size.Small" OverrideStyles="false">
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
@if (!string.IsNullOrWhiteSpace(node.Avatar))
|
||||
{
|
||||
<MudAvatar Size="@GetAvatarSize(Level)">
|
||||
<MudImage ObjectFit="ObjectFit.Cover"
|
||||
ObjectPosition="ObjectPosition.Center"
|
||||
Src="@node.Avatar" />
|
||||
<AppImage Path="@node.Avatar"
|
||||
ObjectFit="ObjectFit.Cover"
|
||||
ObjectPosition="ObjectPosition.Center" />
|
||||
</MudAvatar>
|
||||
}
|
||||
else
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
@attribute [Route(RouteConstants.Profile.Hub)]
|
||||
|
||||
<PageTitle>پروفایل</PageTitle>
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.Small" Class="py-6">
|
||||
<MudStack AlignItems="AlignItems.Center" Spacing="3" Class="mb-6">
|
||||
<MudAvatar Size="Size.Large" Color="Color.Primary" Variant="Variant.Filled" Class="profile-hub-avatar">
|
||||
<MudIcon Icon="@Icons.Material.Outlined.Person" Size="Size.Large" />
|
||||
</MudAvatar>
|
||||
<MudStack Spacing="0" AlignItems="AlignItems.Center">
|
||||
<MudText Typo="Typo.h6" Class="fw-bold">
|
||||
@($"{_userProfile.FirstName} {_userProfile.LastName}")
|
||||
</MudText>
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary">@(_userProfile.Mobile)</MudText>
|
||||
</MudStack>
|
||||
</MudStack>
|
||||
|
||||
<MudStack Spacing="2">
|
||||
<MudLink Href="@RouteConstants.Profile.Personal" Underline="Underline.None">
|
||||
<MudPaper Elevation="0" Class="pa-4 rounded-xl profile-hub-item">
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween">
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="3">
|
||||
<MudIcon Icon="@Icons.Material.Outlined.Person" Color="Color.Primary" />
|
||||
<MudStack Spacing="0">
|
||||
<MudText Typo="Typo.subtitle2">اطلاعات شخصی</MudText>
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">نام، کد ملی و تاریخ تولد</MudText>
|
||||
</MudStack>
|
||||
</MudStack>
|
||||
<MudIcon Icon="@Icons.Material.Filled.ChevronLeft" Class="mud-text-secondary" />
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
</MudLink>
|
||||
|
||||
<MudLink Href="@RouteConstants.Profile.Addresses" Underline="Underline.None">
|
||||
<MudPaper Elevation="0" Class="pa-4 rounded-xl profile-hub-item">
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween">
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="3">
|
||||
<MudIcon Icon="@Icons.Material.Outlined.LocationOn" Color="Color.Success" />
|
||||
<MudStack Spacing="0">
|
||||
<MudText Typo="Typo.subtitle2">آدرسها</MudText>
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">مدیریت آدرسهای تحویل</MudText>
|
||||
</MudStack>
|
||||
</MudStack>
|
||||
<MudIcon Icon="@Icons.Material.Filled.ChevronLeft" Class="mud-text-secondary" />
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
</MudLink>
|
||||
|
||||
<MudLink Href="@RouteConstants.Profile.Settings" Underline="Underline.None">
|
||||
<MudPaper Elevation="0" Class="pa-4 rounded-xl profile-hub-item">
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween">
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="3">
|
||||
<MudIcon Icon="@Icons.Material.Outlined.Settings" Color="Color.Warning" />
|
||||
<MudStack Spacing="0">
|
||||
<MudText Typo="Typo.subtitle2">تنظیمات</MudText>
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">تغییر رمز و تنظیمات حساب</MudText>
|
||||
</MudStack>
|
||||
</MudStack>
|
||||
<MudIcon Icon="@Icons.Material.Filled.ChevronLeft" Class="mud-text-secondary" />
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
</MudLink>
|
||||
</MudStack>
|
||||
</MudContainer>
|
||||
@@ -0,0 +1,34 @@
|
||||
using CMSMicroservice.Protobuf.Protos.User;
|
||||
using FrontOffice.Main.Utilities;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace FrontOffice.Main.Pages.Profile;
|
||||
|
||||
public partial class Hub
|
||||
{
|
||||
[Inject] private UserContract.UserContractClient UserContract { get; set; } = default!;
|
||||
|
||||
private GetUserResponse _userProfile = new();
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
await base.OnAfterRenderAsync(firstRender);
|
||||
if (firstRender)
|
||||
{
|
||||
await LoadUserProfile();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadUserProfile()
|
||||
{
|
||||
try
|
||||
{
|
||||
_userProfile = await UserContract.GetUserAsync(request: new());
|
||||
}
|
||||
catch
|
||||
{
|
||||
_userProfile = new GetUserResponse();
|
||||
}
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
@@ -5,299 +5,237 @@
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
||||
<MudGrid Spacing="4">
|
||||
<!-- Profile Header -->
|
||||
|
||||
@* ═══════════════════════════════════════════════
|
||||
1. PROFILE HEADER — Modern gradient card
|
||||
═══════════════════════════════════════════════ *@
|
||||
<MudItem xs="12">
|
||||
<MudPaper Elevation="4" Class="pa-6">
|
||||
<MudStack Spacing="4">
|
||||
<MudStack Row="true" Spacing="3" AlignItems="AlignItems.Center">
|
||||
<MudStack Row="true" Spacing="3" AlignItems="AlignItems.Center">
|
||||
<MudAvatar Size="Size.Large" Color="Color.Primary">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Person" Size="Size.Large" />
|
||||
</MudAvatar>
|
||||
<div>
|
||||
<MudText Typo="Typo.h5">@($"{_userProfile.FirstName} {_userProfile.LastName}")</MudText>
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary">@(_userProfile.Mobile)</MudText>
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">عضو از @(_userProfile.MobileVerifiedAt?.ToDateTime().MiladiToJalali())</MudText>
|
||||
</div>
|
||||
</MudStack>
|
||||
<MudSpacer/>
|
||||
|
||||
<MudButton Variant="Variant.Outlined" Href="https://dayadiamond.ir/" >سامانه دایا</MudButton>
|
||||
<MudPaper Elevation="0" Class="dash-header-card pa-5 pa-md-6 rounded-xl">
|
||||
<MudStack Row="true" Spacing="3" AlignItems="AlignItems.Center" Class="flex-wrap">
|
||||
<MudAvatar Size="Size.Large" Class="dash-avatar">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Person" Size="Size.Large" />
|
||||
</MudAvatar>
|
||||
<MudStack Spacing="0" Class="flex-grow-1">
|
||||
<MudText Typo="Typo.h5" Class="dash-hero-name">
|
||||
@($"{_userProfile.FirstName} {_userProfile.LastName}")
|
||||
</MudText>
|
||||
<MudText Typo="Typo.body2" Class="dash-hero-sub">
|
||||
@(_userProfile.Mobile)
|
||||
</MudText>
|
||||
<MudText Typo="Typo.caption" Class="dash-hero-hint">
|
||||
عضو از @(_userProfile.MobileVerifiedAt?.ToDateTime().MiladiToJalali())
|
||||
</MudText>
|
||||
</MudStack>
|
||||
|
||||
|
||||
<!-- Referral Code Section -->
|
||||
<MudDivider />
|
||||
|
||||
@if (CanShowReferralLink)
|
||||
{
|
||||
<MudStack Spacing="3">
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center">
|
||||
<MudText Typo="Typo.subtitle1">کد دعوت شما</MudText>
|
||||
<MudSpacer />
|
||||
<MudButton Variant="Variant.Outlined"
|
||||
Color="Color.Primary"
|
||||
StartIcon="@Icons.Material.Filled.Share"
|
||||
OnClick="ShareReferralCode">
|
||||
اشتراکگذاری
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary">
|
||||
این کد را با دوستان خود به اشتراک بگذارید تا از خریدهای آنها پاداش دریافت کنید.
|
||||
</MudText>
|
||||
|
||||
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
|
||||
<MudTextField @bind-Value="_userProfile.ReferralCode"
|
||||
Label="کد دعوت"
|
||||
Variant="Variant.Outlined"
|
||||
ReadOnly="true"
|
||||
Class="flex-grow-1"
|
||||
Adornment="Adornment.End"
|
||||
AdornmentIcon="@Icons.Material.Filled.ContentCopy"
|
||||
OnAdornmentClick="CopyReferralCode" />
|
||||
</MudStack>
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(_copyMessage))
|
||||
{
|
||||
<MudText Typo="Typo.caption" Color="Color.Success">@(_copyMessage)</MudText>
|
||||
}
|
||||
</MudStack>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudPaper Class="my-3 pa-3 border border-secondary">
|
||||
<MudStack Spacing="3" AlignItems="AlignItems.Center">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Lock" Size="Size.Large" Color="Color.Info" />
|
||||
<MudText Typo="Typo.h6" Align="Align.Center" Color="Color.Info">
|
||||
لینک دعوت شما هنوز فعال نشده است
|
||||
</MudText>
|
||||
<MudText Typo="Typo.body1" Align="Align.Center" Class="mud-text-secondary">
|
||||
برای فعالسازی لینک دعوت و دعوت دوستان خود به جمع مشتریان ویژه کارابازار، کافی است ابتدا <strong>پکیج پایه ۵۶ میلیونی</strong> را تهیه کنید و سپس در <strong>باشگاه مشتریان</strong> عضو شوید.
|
||||
</MudText>
|
||||
<MudText Typo="Typo.body2" Align="Align.Center" Class="mud-text-secondary">
|
||||
از مزایای ویژه عضویت در باشگاه مشتریان کارابازار سلامت بهرهمند شده و با فعالیت در زمینه توسعه فروشگاهها پاداش دریافت کنید.
|
||||
</MudText>
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
Size="Size.Large"
|
||||
StartIcon="@Icons.Material.Filled.ShoppingCart"
|
||||
OnClick="OpenPurchaseOptions">
|
||||
شروع فرآیند تامین اعتبار
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
}
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Class="rounded-pill dash-hero-btn"
|
||||
Href="https://dayadiamond.ir/"
|
||||
StartIcon="@Icons.Material.Filled.Diamond">
|
||||
سامانه دایا
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
|
||||
<!-- Wallet Highlight Row -->
|
||||
@* ═══════════════════════════════════════════════
|
||||
2. WALLET STRIP — Compact inline stats
|
||||
═══════════════════════════════════════════════ *@
|
||||
<MudItem xs="12">
|
||||
<MudPaper Elevation="3" Class="pa-4 rounded-lg gradient-border">
|
||||
<MudGrid Spacing="2" AlignItems="AlignItems.Center">
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudStack>
|
||||
<MudText Typo="Typo.subtitle2" Class="mud-text-secondary">موجودی اعتباری</MudText>
|
||||
<MudText Typo="Typo.h5" Color="Color.Primary">@_walletCredit</MudText>
|
||||
</MudStack>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudStack>
|
||||
<MudText Typo="Typo.subtitle2" Class="mud-text-secondary">موجودی تخفیف باشگاه</MudText>
|
||||
<MudText Typo="Typo.h5" Color="Color.Primary">@_walletDiscount</MudText>
|
||||
</MudStack>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudStack>
|
||||
<MudText Typo="Typo.subtitle2" Class="mud-text-secondary">موجودی پاداش تیمی</MudText>
|
||||
<MudText Typo="Typo.h5" Color="Color.Primary">@_walletNetwork</MudText>
|
||||
</MudStack>
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="4" Class="d-flex justify-end">
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" StartIcon="@Icons.Material.Filled.AccountBalanceWallet" Href="@RouteConstants.Profile.Wallet">جزئیات کیف پول</MudButton>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
<MudPaper Elevation="1" Class="pa-2 pa-md-4 rounded-xl">
|
||||
<MudStack Row="true" Justify="Justify.SpaceAround" AlignItems="AlignItems.Center" Class="flex-wrap wallet-strip">
|
||||
<div class="wallet-strip-item">
|
||||
<div class="wallet-strip-label">
|
||||
<MudIcon Icon="@Icons.Material.Outlined.CreditCard" Size="Size.Small" Color="Color.Primary" />
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">اعتباری</MudText>
|
||||
</div>
|
||||
<MudText Typo="Typo.subtitle2" Color="Color.Primary">@_walletCredit</MudText>
|
||||
</div>
|
||||
<MudDivider Vertical="true" FlexItem="true" Class="d-none d-sm-flex" />
|
||||
<div class="wallet-strip-item">
|
||||
<div class="wallet-strip-label">
|
||||
<MudIcon Icon="@Icons.Material.Outlined.Discount" Size="Size.Small" Color="Color.Success" />
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">تخفیف باشگاه</MudText>
|
||||
</div>
|
||||
<MudText Typo="Typo.subtitle2" Color="Color.Success">@_walletDiscount</MudText>
|
||||
</div>
|
||||
<MudDivider Vertical="true" FlexItem="true" Class="d-none d-sm-flex" />
|
||||
<div class="wallet-strip-item">
|
||||
<div class="wallet-strip-label">
|
||||
<MudIcon Icon="@Icons.Material.Outlined.Groups" Size="Size.Small" Color="Color.Warning" />
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">پاداش تیمی</MudText>
|
||||
</div>
|
||||
<MudText Typo="Typo.subtitle2" Color="Color.Warning">@_walletNetwork</MudText>
|
||||
</div>
|
||||
<MudDivider Vertical="true" FlexItem="true" Class="d-none d-sm-flex" />
|
||||
<MudButton Variant="Variant.Text" Color="Color.Primary" Size="Size.Small"
|
||||
Href="@RouteConstants.Profile.Wallet" EndIcon="@Icons.Material.Filled.ArrowBack">
|
||||
کیف پول
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
|
||||
<!-- Profile Content as tiles -->
|
||||
@* ═══════════════════════════════════════════════
|
||||
3. REFERRAL SECTION
|
||||
═══════════════════════════════════════════════ *@
|
||||
<MudItem xs="12">
|
||||
<MudPaper Elevation="4" Class="pa-4">
|
||||
<MudGrid Spacing="3">
|
||||
<MudItem xs="6" sm="6" md="3">
|
||||
<MudLink Href="@RouteConstants.Profile.Personal" Underline="Underline.None" Class="tile-link">
|
||||
<MudCard Elevation="1" Class="rounded-lg profile-tile">
|
||||
<MudCardContent Class="d-flex flex-column align-center pa-4">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Person" Size="Size.Large" Color="Color.Primary" />
|
||||
<MudText Typo="Typo.subtitle1" Class="mt-2">اطلاعات شخصی</MudText>
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">نمایش و ویرایش اطلاعات</MudText>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudLink>
|
||||
</MudItem>
|
||||
<MudItem xs="6" sm="6" md="3">
|
||||
<MudLink Href="@RouteConstants.Profile.Addresses" Underline="Underline.None" Class="tile-link">
|
||||
<MudCard Elevation="1" Class="rounded-lg profile-tile">
|
||||
<MudCardContent Class="d-flex flex-column align-center pa-4">
|
||||
<MudIcon Icon="@Icons.Material.Filled.LocationOn" Size="Size.Large" Color="Color.Primary" />
|
||||
<MudText Typo="Typo.subtitle1" Class="mt-2">آدرسها</MudText>
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">مدیریت آدرسهای شما</MudText>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudLink>
|
||||
</MudItem>
|
||||
<MudItem xs="6" sm="6" md="3">
|
||||
<MudLink Href="@RouteConstants.Profile.Tree" Underline="Underline.None" Class="tile-link">
|
||||
<MudCard Elevation="1" Class="rounded-lg profile-tile">
|
||||
<MudCardContent Class="d-flex flex-column align-center pa-4">
|
||||
<MudIcon Icon="@Icons.Material.Filled.AccountTree" Size="Size.Large" Color="Color.Primary" />
|
||||
<MudText Typo="Typo.subtitle1" Class="mt-2">شجرهنامه</MudText>
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">ساختار تیم</MudText>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudLink>
|
||||
</MudItem>
|
||||
<MudItem xs="6" sm="6" md="3">
|
||||
<MudLink Href="@RouteConstants.Profile.Settings" Underline="Underline.None" Class="tile-link">
|
||||
<MudCard Elevation="1" Class="rounded-lg profile-tile">
|
||||
<MudCardContent Class="d-flex flex-column align-center pa-4">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Settings" Size="Size.Large" Color="Color.Primary" />
|
||||
<MudText Typo="Typo.subtitle1" Class="mt-2">تنظیمات حساب</MudText>
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">اعلانها و تنظیمات</MudText>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudLink>
|
||||
</MudItem>
|
||||
<MudItem xs="6" sm="6" md="3">
|
||||
<MudLink Href="@RouteConstants.Store.Products" Underline="Underline.None" Class="tile-link">
|
||||
<MudCard Elevation="1" Class="rounded-lg profile-tile">
|
||||
<MudCardContent Class="d-flex flex-column align-center pa-4">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Storefront" Size="Size.Large" Color="Color.Primary" />
|
||||
<MudText Typo="Typo.subtitle1" Class="mt-2">فروشگاه</MudText>
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">مشاهده و خرید محصولات</MudText>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudLink>
|
||||
</MudItem>
|
||||
<MudItem xs="6" sm="6" md="3">
|
||||
<MudLink Href="@RouteConstants.Profile.Wallet" Underline="Underline.None" Class="tile-link">
|
||||
<MudCard Elevation="1" Class="rounded-lg profile-tile">
|
||||
<MudCardContent Class="d-flex flex-column align-center pa-4">
|
||||
<MudIcon Icon="@Icons.Material.Filled.AccountBalanceWallet" Size="Size.Large" Color="Color.Primary" />
|
||||
<MudText Typo="Typo.subtitle1" Class="mt-2">کیف پول</MudText>
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">مدیریت و تاریخچه</MudText>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudLink>
|
||||
</MudItem>
|
||||
<MudItem xs="6" sm="6" md="3">
|
||||
<MudLink Href="@RouteConstants.Profile.WithdrawalRequests" Underline="Underline.None" Class="tile-link">
|
||||
<MudCard Elevation="1" Class="rounded-lg profile-tile">
|
||||
<MudCardContent Class="d-flex flex-column align-center pa-4">
|
||||
<MudIcon Icon="@Icons.Material.Filled.RequestPage" Size="Size.Large" Color="Color.Warning" />
|
||||
<MudText Typo="Typo.subtitle1" Class="mt-2">درخواست برداشت</MudText>
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">ثبت و پیگیری برداشت</MudText>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudLink>
|
||||
</MudItem>
|
||||
<MudItem xs="6" sm="6" md="3">
|
||||
<MudLink Href="@RouteConstants.Club.Membership" Underline="Underline.None" Class="tile-link">
|
||||
<MudCard Elevation="1" Class="rounded-lg profile-tile">
|
||||
<MudCardContent Class="d-flex flex-column align-center pa-4">
|
||||
<MudIcon Icon="@Icons.Material.Filled.CardMembership" Size="Size.Large" Color="Color.Secondary" />
|
||||
<MudText Typo="Typo.subtitle1" Class="mt-2">باشگاه مشتریان</MudText>
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">عضویت و مزایا</MudText>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudLink>
|
||||
</MudItem>
|
||||
<MudItem xs="6" sm="6" md="3">
|
||||
<MudLink Href="@RouteConstants.Network.Statistics" Underline="Underline.None" Class="tile-link">
|
||||
<MudCard Elevation="1" Class="rounded-lg profile-tile">
|
||||
<MudCardContent Class="d-flex flex-column align-center pa-4">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Groups" Size="Size.Large" Color="Color.Info" />
|
||||
<MudText Typo="Typo.subtitle1" Class="mt-2">آمار باشگاه</MudText>
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">آمار جزئیات باشگاه</MudText>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudLink>
|
||||
</MudItem>
|
||||
<MudItem xs="6" sm="6" md="3">
|
||||
<MudLink Href="@RouteConstants.Commission.Dashboard" Underline="Underline.None" Class="tile-link">
|
||||
<MudCard Elevation="1" Class="rounded-lg profile-tile">
|
||||
<MudCardContent Class="d-flex flex-column align-center pa-4">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Payments" Size="Size.Large" Color="Color.Success" />
|
||||
<MudText Typo="Typo.subtitle1" Class="mt-2">پاداش</MudText>
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">درآمد و تاریخچه</MudText>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudLink>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudPaper>
|
||||
@if (CanShowReferralLink)
|
||||
{
|
||||
<MudPaper Elevation="1" Class="pa-5 rounded-xl">
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2" Class="mb-3">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Share" Color="Color.Primary" />
|
||||
<MudText Typo="Typo.h6">کد دعوت شما</MudText>
|
||||
<MudSpacer />
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
Size="Size.Small"
|
||||
Class="rounded-pill"
|
||||
StartIcon="@Icons.Material.Filled.Share"
|
||||
OnClick="ShareReferralCode">
|
||||
اشتراکگذاری
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary mb-3">
|
||||
این کد را با دوستان خود به اشتراک بگذارید تا از خریدهای آنها پاداش دریافت کنید.
|
||||
</MudText>
|
||||
<MudTextField @bind-Value="_userProfile.ReferralCode"
|
||||
Label="کد دعوت"
|
||||
Variant="Variant.Outlined"
|
||||
ReadOnly="true"
|
||||
Adornment="Adornment.End"
|
||||
AdornmentIcon="@Icons.Material.Filled.ContentCopy"
|
||||
OnAdornmentClick="CopyReferralCode"
|
||||
Class="rounded-lg" />
|
||||
@if (!string.IsNullOrWhiteSpace(_copyMessage))
|
||||
{
|
||||
<MudText Typo="Typo.caption" Color="Color.Success" Class="mt-2">@(_copyMessage)</MudText>
|
||||
}
|
||||
</MudPaper>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudPaper Elevation="0" Class="pa-6 rounded-xl text-center" Style="border:2px dashed var(--mud-palette-primary); background:rgba(99,102,241,.04);">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Lock" Size="Size.Large" Color="Color.Primary" Class="mb-2" Style="font-size:2.5rem;" />
|
||||
<MudText Typo="Typo.h6" Color="Color.Primary" Class="mb-2">
|
||||
لینک دعوت شما هنوز فعال نشده است
|
||||
</MudText>
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary mb-1" Style="max-width:560px; margin:0 auto;">
|
||||
برای فعالسازی لینک دعوت، ابتدا <strong>پکیج پایه</strong> را تهیه کنید و سپس در <strong>باشگاه مشتریان</strong> عضو شوید.
|
||||
</MudText>
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary mb-4" Style="max-width:560px; margin:0 auto;">
|
||||
از مزایای ویژه عضویت بهرهمند شده و با فعالیت در زمینه توسعه فروشگاهها پاداش دریافت کنید.
|
||||
</MudText>
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
Size="Size.Large"
|
||||
Class="rounded-pill"
|
||||
StartIcon="@Icons.Material.Filled.ShoppingCart"
|
||||
OnClick="OpenPurchaseOptions">
|
||||
شروع فرآیند تامین اعتبار
|
||||
</MudButton>
|
||||
</MudPaper>
|
||||
}
|
||||
</MudItem>
|
||||
|
||||
@* ═══════════════════════════════════════════════
|
||||
3.5 LATEST BLOG BANNER
|
||||
═══════════════════════════════════════════════ *@
|
||||
@if (_latestPost is not null)
|
||||
{
|
||||
<MudItem xs="12">
|
||||
<MudLink Href="@($"/blog/{_latestPost.Slug}")" Underline="Underline.None">
|
||||
<MudPaper Elevation="1" Class="rounded-xl dash-blog-banner overflow-hidden">
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Class="pa-2" Spacing="2">
|
||||
@if (!string.IsNullOrWhiteSpace(_latestPost.ThumbnailUrl))
|
||||
{
|
||||
<div style="width:72px;height:72px;min-width:72px;border-radius:10px;overflow:hidden;">
|
||||
<AppImage Path="@_latestPost.ThumbnailUrl" Alt="@_latestPost.Title"
|
||||
ObjectFit="ObjectFit.Cover" Style="width:100%;height:100%;" />
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudAvatar Rounded="true" Size="Size.Large" Color="Color.Primary" Variant="Variant.Outlined">
|
||||
<MudIcon Icon="@Icons.Material.Outlined.Article" />
|
||||
</MudAvatar>
|
||||
}
|
||||
<MudStack Spacing="0" Class="flex-grow-1" Style="min-width:0;">
|
||||
<MudText Typo="Typo.caption" Color="Color.Primary" Style="font-size:0.7rem;">جدیدترین مطلب</MudText>
|
||||
<MudText Typo="Typo.body2" Class="dash-blog-title" Style="font-size:0.85rem;">@_latestPost.Title</MudText>
|
||||
</MudStack>
|
||||
<MudIcon Icon="@Icons.Material.Filled.ArrowBack" Color="Color.Primary" Size="Size.Small" />
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
</MudLink>
|
||||
</MudItem>
|
||||
}
|
||||
|
||||
@* ═══════════════════════════════════════════════
|
||||
4. QUICK ACCESS TILES — Modern grid
|
||||
═══════════════════════════════════════════════ *@
|
||||
<MudItem xs="12">
|
||||
<MudText Typo="Typo.h6" Class="mb-3">دسترسی سریع</MudText>
|
||||
<MudGrid Spacing="3" Justify="Justify.Center">
|
||||
@foreach (var tile in _dashTiles)
|
||||
{
|
||||
<MudItem xs="6" sm="4" md="3">
|
||||
<MudLink Href="@tile.Href" Underline="Underline.None" Class="tile-link">
|
||||
<MudPaper Elevation="0" Class="pa-4 rounded-xl dash-tile text-center">
|
||||
<MudAvatar Size="Size.Medium" Class="mx-auto mb-2" Style="@tile.AvatarStyle">
|
||||
<MudIcon Icon="@tile.Icon" Size="Size.Medium" />
|
||||
</MudAvatar>
|
||||
<MudText Typo="Typo.subtitle2">@tile.Title</MudText>
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">@tile.Subtitle</MudText>
|
||||
</MudPaper>
|
||||
</MudLink>
|
||||
</MudItem>
|
||||
}
|
||||
</MudGrid>
|
||||
</MudItem>
|
||||
|
||||
</MudGrid>
|
||||
</MudContainer>
|
||||
|
||||
<!-- Purchase Options Bottom Sheet -->
|
||||
@* ── Purchase Options Dialog ── *@
|
||||
<MudDialog @bind-Visible="_showPurchaseBottomSheet">
|
||||
<DialogContent>
|
||||
<MudStack Spacing="3" Class="pa-2">
|
||||
<MudText Typo="Typo.h6" Align="Align.Center">
|
||||
خرید پکیج پایه ۵۶ میلیون تومان
|
||||
</MudText>
|
||||
|
||||
<MudDivider />
|
||||
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary">
|
||||
برای خرید پکیج پایه، یکی از روشهای زیر را انتخاب کنید:
|
||||
</MudText>
|
||||
|
||||
<!-- Direct Payment Option -->
|
||||
<MudPaper Elevation="0" Class="pa-4 rounded-lg" Style="border: 2px solid var(--mud-palette-primary);">
|
||||
<MudStack Spacing="2">
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
|
||||
<MudIcon Icon="@Icons.Material.Filled.CreditCard" Color="Color.Primary" Size="Size.Medium" />
|
||||
<MudText Typo="Typo.subtitle1" Color="Color.Primary"><b>پرداخت مستقیم</b></MudText>
|
||||
</MudStack>
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary">
|
||||
پرداخت آنی از طریق درگاه بانکی
|
||||
</MudText>
|
||||
<MudButton Variant="Variant.Filled" Disabled="true"
|
||||
Color="Color.Primary"
|
||||
FullWidth="true"
|
||||
StartIcon="@Icons.Material.Filled.Payment"
|
||||
OnClick="DirectPayment">
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary">پرداخت آنی از طریق درگاه بانکی</MudText>
|
||||
<MudButton Variant="Variant.Filled" Disabled="true" Color="Color.Primary" FullWidth="true"
|
||||
StartIcon="@Icons.Material.Filled.Payment" OnClick="DirectPayment">
|
||||
پرداخت با کارت بانکی(بزودی)
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
|
||||
<!-- Daya Loan Option -->
|
||||
<MudPaper Elevation="0" Class="pa-4 rounded-lg" Style="border: 2px solid var(--mud-palette-tertiary);">
|
||||
<MudStack Spacing="2">
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Diamond" Color="Color.Tertiary" Size="Size.Medium" />
|
||||
<MudText Typo="Typo.subtitle1" Color="Color.Tertiary"><b>اعتبار الماسی دایا</b></MudText>
|
||||
</MudStack>
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary">
|
||||
تأمین اعتبار از طریق خرید توکن الماس و دریافت وام
|
||||
</MudText>
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Tertiary"
|
||||
FullWidth="true"
|
||||
StartIcon="@Icons.Material.Filled.AccountBalance"
|
||||
OnClick="DayaLoanPayment">
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary">تأمین اعتبار از طریق خرید توکن الماس و دریافت وام</MudText>
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Tertiary" FullWidth="true"
|
||||
StartIcon="@Icons.Material.Filled.AccountBalance" OnClick="DayaLoanPayment">
|
||||
تأمین اعتبار الماسی
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
|
||||
<MudButton Variant="Variant.Text"
|
||||
Color="Color.Default"
|
||||
FullWidth="true"
|
||||
OnClick="ClosePurchaseOptions">
|
||||
<MudButton Variant="Variant.Text" Color="Color.Default" FullWidth="true" OnClick="ClosePurchaseOptions">
|
||||
بستن
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
using FluentValidation;
|
||||
using FrontOffice.BFF.User.Protobuf.Protos.User;
|
||||
using FrontOffice.BFF.User.Protobuf.Validator;
|
||||
using FrontOffice.BFF.UserAddress.Protobuf.Protos.UserAddress;
|
||||
using FrontOffice.BFF.UserAddress.Protobuf.Validator;
|
||||
using FrontOffice.BFF.Package.Protobuf.Protos.Package;
|
||||
|
||||
using FrontOffice.Main.Pages.Profile.Components;
|
||||
using FrontOffice.Main.Utilities;
|
||||
using Mapster;
|
||||
@@ -11,6 +7,11 @@ using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.JSInterop;
|
||||
using MudBlazor;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using CMSMicroservice.Protobuf.Protos.Package;
|
||||
using CMSMicroservice.Protobuf.Protos.User;
|
||||
using CMSMicroservice.Protobuf.Protos.UserAddress;
|
||||
using CMSMicroservice.Protobuf.Validator.User;
|
||||
using CMSMicroservice.Protobuf.Validator.UserAddress;
|
||||
using Severity = MudBlazor.Severity;
|
||||
|
||||
namespace FrontOffice.Main.Pages.Profile;
|
||||
@@ -21,10 +22,11 @@ public partial class Index
|
||||
[Inject] private UserAddressContract.UserAddressContractClient UserAddressContract { get; set; } = default!;
|
||||
[Inject] private PackageContract.PackageContractClient PackageContract { get; set; } = default!;
|
||||
[Inject] private AuthService AuthService { get; set; } = default!;
|
||||
[Inject] private BlogPostService BlogPostService { get; set; } = default!;
|
||||
|
||||
private GetUserResponse _userProfile = new();
|
||||
private UpdateUserRequest _updateUserRequest = new();
|
||||
private readonly UpdateUserRequestValidator _personalValidator = new();
|
||||
private GetUserForCustomerResponse _userProfile = new();
|
||||
private BlogPostCardDto? _latestPost;
|
||||
private UpdateCustomerProfileRequest _updateUserRequest = new();
|
||||
|
||||
private MudForm? _personalForm;
|
||||
|
||||
@@ -44,8 +46,24 @@ public partial class Index
|
||||
private bool _showPurchaseBottomSheet;
|
||||
private bool _isProcessingPayment;
|
||||
|
||||
// Dashboard Tiles
|
||||
private readonly List<DashTile> _dashTiles = new()
|
||||
{
|
||||
new(RouteConstants.Profile.Personal, Icons.Material.Filled.Person, "اطلاعات شخصی", "نمایش و ویرایش", "background:rgba(99,102,241,.12); color:#6366f1;"),
|
||||
new(RouteConstants.Profile.Addresses, Icons.Material.Filled.LocationOn, "آدرسها", "مدیریت آدرسها", "background:rgba(99,102,241,.12); color:#6366f1;"),
|
||||
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.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;"),
|
||||
new(RouteConstants.Commission.Dashboard, Icons.Material.Filled.Payments, "پاداش", "درآمد و تاریخچه", "background:rgba(16,185,129,.12); color:#10b981;"),
|
||||
};
|
||||
|
||||
private record DashTile(string Href, string Icon, string Title, string Subtitle, string AvatarStyle);
|
||||
|
||||
// Address management
|
||||
private List<GetAllUserAddressByFilterResponseModel> _addresses = new();
|
||||
private List<CustomerAddressModel> _addresses = new();
|
||||
private bool _isLoadingAddresses;
|
||||
private CreateNewUserAddressRequest _newAddressRequest = new();
|
||||
private UpdateUserAddressRequest _editAddressRequest = new();
|
||||
@@ -63,6 +81,7 @@ public partial class Index
|
||||
await LoadUserProfile();
|
||||
await LoadAddresses();
|
||||
await LoadWallet();
|
||||
await LoadLatestPost();
|
||||
|
||||
// نمایش Modal قرارداد باشگاه اگر پکیج خریده ولی باشگاه فعال نشده
|
||||
await CheckAndShowClubContractModal();
|
||||
@@ -133,19 +152,47 @@ public partial class Index
|
||||
|
||||
private static string FormatPrice(long price) => string.Format("{0:N0} تومان", price);
|
||||
|
||||
private async Task LoadLatestPost()
|
||||
{
|
||||
try
|
||||
{
|
||||
var posts = await BlogPostService.GetFeaturedPostsAsync(1);
|
||||
if (!posts.Any())
|
||||
{
|
||||
var result = await BlogPostService.GetPublishedPostsAsync(page: 1, pageSize: 1);
|
||||
posts = result.Posts;
|
||||
}
|
||||
_latestPost = posts.FirstOrDefault();
|
||||
}
|
||||
catch
|
||||
{
|
||||
_latestPost = null;
|
||||
}
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task LoadUserProfile()
|
||||
{
|
||||
try
|
||||
{
|
||||
_userProfile = await UserContract.GetUserAsync(request: new());
|
||||
_updateUserRequest = _userProfile.Adapt<UpdateUserRequest>();
|
||||
_userProfile = await UserContract.GetUserForCustomerAsync(new());
|
||||
_updateUserRequest = new UpdateCustomerProfileRequest
|
||||
{
|
||||
FirstName = _userProfile.FirstName,
|
||||
LastName = _userProfile.LastName,
|
||||
Email = _userProfile.Email,
|
||||
NationalCode = _userProfile.NationalCode,
|
||||
};
|
||||
if (_userProfile.BirthDate != null)
|
||||
{
|
||||
_date = _userProfile.BirthDate.ToDateTime();
|
||||
_updateUserRequest.BirthDate = _userProfile.BirthDate;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Handle the case when user is not authenticated or API fails
|
||||
_userProfile = new GetUserResponse();
|
||||
_userProfile = new GetUserForCustomerResponse();
|
||||
}
|
||||
StateHasChanged();
|
||||
}
|
||||
@@ -167,7 +214,7 @@ public partial class Index
|
||||
if (_date != null)
|
||||
_updateUserRequest.BirthDate = _date.Value.DateTimeToTimestamp();
|
||||
|
||||
await UserContract.UpdateUserAsync(request: _updateUserRequest);
|
||||
await UserContract.UpdateCustomerProfileAsync(request: _updateUserRequest);
|
||||
await LoadUserProfile();
|
||||
Snackbar.Add("اطلاعات شخصی با موفقیت ذخیره شد.", Severity.Success);
|
||||
}
|
||||
@@ -250,20 +297,20 @@ public partial class Index
|
||||
_isLoadingAddresses = true;
|
||||
try
|
||||
{
|
||||
var response = await UserAddressContract.GetAllUserAddressByFilterAsync(request: new());
|
||||
var response = await UserAddressContract.GetCustomerAddressesAsync(request: new());
|
||||
if (response?.Models?.Any() == true)
|
||||
{
|
||||
_addresses = response.Models.ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
_addresses = new List<GetAllUserAddressByFilterResponseModel>();
|
||||
_addresses = new List<CustomerAddressModel>();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"خطا در بارگذاری آدرسها: {ex.Message}", Severity.Error);
|
||||
_addresses = new List<GetAllUserAddressByFilterResponseModel>();
|
||||
_addresses = new List<CustomerAddressModel>();
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -283,7 +330,7 @@ public partial class Index
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OpenEditAddressDialog(GetAllUserAddressByFilterResponseModel address)
|
||||
private async Task OpenEditAddressDialog(CustomerAddressModel address)
|
||||
{
|
||||
var dialog = await DialogService.ShowAsync<EditAddressDialog>("ویرایش آدرس", new DialogParameters<EditAddressDialog>
|
||||
{
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
@page "/profile/payment-callback"
|
||||
@attribute [Authorize]
|
||||
@using Blazored.LocalStorage
|
||||
@using FrontOffice.BFF.Package.Protobuf.Protos.Package
|
||||
@using FrontOffice.BFF.User.Protobuf.Protos.User
|
||||
@using CMSMicroservice.Protobuf.Protos.Package
|
||||
@using CMSMicroservice.Protobuf.Protos.User
|
||||
@inject IJSRuntime JS
|
||||
|
||||
|
||||
<PageTitle>نتیجه پرداخت | کارا بازار سلامت</PageTitle>
|
||||
|
||||
@@ -36,7 +38,7 @@
|
||||
</MudText>
|
||||
|
||||
<MudButton Color="Color.Primary" Variant="Variant.Filled" Class="mt-6"
|
||||
Href="/profile">
|
||||
OnClick="GoBack">
|
||||
بازگشت به پروفایل
|
||||
</MudButton>
|
||||
}
|
||||
@@ -47,7 +49,7 @@
|
||||
<MudText Typo="Typo.body1" Class="mt-2">@_message</MudText>
|
||||
|
||||
<MudButton Color="Color.Primary" Variant="Variant.Filled" Class="mt-6"
|
||||
Href="/profile">
|
||||
OnClick="GoBack">
|
||||
بازگشت به پروفایل
|
||||
</MudButton>
|
||||
|
||||
@@ -112,13 +114,14 @@
|
||||
{
|
||||
OrderId = OrderId,
|
||||
TransactionId = TransactionId,
|
||||
Authority = Authority,
|
||||
Status = Status ?? "NOK"
|
||||
PaymentSuccess = Status == "OK",
|
||||
RefId = !string.IsNullOrEmpty(Authority) ? Authority : null,
|
||||
Message = !string.IsNullOrEmpty(Status) ? Status : null
|
||||
});
|
||||
|
||||
_isSuccess = response.Success;
|
||||
_message = response.Message;
|
||||
_refId = response.RefId;
|
||||
_refId = response.ReferenceCode ?? "";
|
||||
_walletBalance = response.WalletBalance;
|
||||
_discountBalance = response.DiscountBalance;
|
||||
|
||||
@@ -153,7 +156,7 @@
|
||||
{
|
||||
try
|
||||
{
|
||||
var userResponse = await UserContractClient.GetUserAsync(new Google.Protobuf.WellKnownTypes.Empty());
|
||||
var userResponse = await UserContractClient.GetUserForCustomerAsync(new CMSMicroservice.Protobuf.Protos.User.GetUserForCustomerRequest());
|
||||
if (!string.IsNullOrWhiteSpace(userResponse.Token))
|
||||
{
|
||||
// ذخیره توکن جدید در localStorage
|
||||
@@ -171,4 +174,6 @@
|
||||
}
|
||||
|
||||
private static string FormatPrice(long price) => string.Format("{0:N0} تومان", price);
|
||||
|
||||
private async Task GoBack() => await JS.InvokeVoidAsync("history.back");
|
||||
}
|
||||
|
||||
@@ -4,13 +4,10 @@
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.Medium" Class="py-6">
|
||||
<MudStack Spacing="3">
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
||||
<MudText Typo="Typo.h5">اطلاعات شخصی</MudText>
|
||||
<MudButton Variant="Variant.Text" StartIcon="@Icons.Material.Filled.ArrowBack" Href="@RouteConstants.Profile.Index">بازگشت</MudButton>
|
||||
</MudStack>
|
||||
<PageHeader Title="اطلاعات شخصی" BackHref="@RouteConstants.Profile.Index" />
|
||||
|
||||
<MudPaper Elevation="2" Class="pa-4 rounded-lg">
|
||||
<MudForm @ref="_personalForm" Model="_updateUserRequest" Validation="@(_personalValidator.ValidateValue)">
|
||||
<MudForm @ref="_personalForm" Model="_updateUserRequest">
|
||||
<MudGrid Spacing="3">
|
||||
<MudItem xs="12" md="6">
|
||||
<MudTextField @bind-Value="_updateUserRequest.FirstName"
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
using FluentValidation;
|
||||
using FrontOffice.BFF.User.Protobuf.Protos.User;
|
||||
using FrontOffice.BFF.User.Protobuf.Validator;
|
||||
using CMSMicroservice.Protobuf.Protos.User;
|
||||
|
||||
using FrontOffice.Main.Utilities;
|
||||
using Mapster;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
using Severity = MudBlazor.Severity;
|
||||
@@ -13,9 +11,8 @@ public partial class Personal : ComponentBase
|
||||
{
|
||||
[Inject] private UserContract.UserContractClient UserContract { get; set; } = default!;
|
||||
|
||||
private GetUserResponse _userProfile = new();
|
||||
private UpdateUserRequest _updateUserRequest = new();
|
||||
private readonly UpdateUserRequestValidator _personalValidator = new();
|
||||
private GetUserForCustomerResponse _userProfile = new();
|
||||
private UpdateCustomerProfileRequest _updateUserRequest = new();
|
||||
|
||||
private MudForm? _personalForm;
|
||||
private DateTime? _date;
|
||||
@@ -30,8 +27,14 @@ public partial class Personal : ComponentBase
|
||||
{
|
||||
try
|
||||
{
|
||||
_userProfile = await UserContract.GetUserAsync(new());
|
||||
_updateUserRequest = _userProfile.Adapt<UpdateUserRequest>();
|
||||
_userProfile = await UserContract.GetUserForCustomerAsync(new());
|
||||
_updateUserRequest = new UpdateCustomerProfileRequest
|
||||
{
|
||||
FirstName = _userProfile.FirstName,
|
||||
LastName = _userProfile.LastName,
|
||||
Email = _userProfile.Email,
|
||||
NationalCode = _userProfile.NationalCode,
|
||||
};
|
||||
if (_userProfile.BirthDate != null)
|
||||
_date = _userProfile.BirthDate.ToDateTime();
|
||||
}
|
||||
@@ -56,7 +59,7 @@ public partial class Personal : ComponentBase
|
||||
if (_date != null)
|
||||
_updateUserRequest.BirthDate = _date.Value.DateTimeToTimestamp();
|
||||
|
||||
await UserContract.UpdateUserAsync(_updateUserRequest);
|
||||
await UserContract.UpdateCustomerProfileAsync(_updateUserRequest);
|
||||
Snackbar.Add("اطلاعات شخصی با موفقیت ذخیره شد.", Severity.Success);
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -4,10 +4,7 @@
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.Medium" Class="py-6">
|
||||
<MudStack Spacing="3">
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
||||
<MudText Typo="Typo.h5">تنظیمات حساب</MudText>
|
||||
<MudButton Variant="Variant.Text" StartIcon="@Icons.Material.Filled.ArrowBack" Href="@RouteConstants.Profile.Index">بازگشت</MudButton>
|
||||
</MudStack>
|
||||
<PageHeader Title="تنظیمات حساب" BackHref="@RouteConstants.Profile.Index" />
|
||||
|
||||
<MudPaper Elevation="2" Class="pa-4 rounded-lg">
|
||||
<MudStack Spacing="4">
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using FrontOffice.BFF.User.Protobuf.Protos.User;
|
||||
using Mapster;
|
||||
using CMSMicroservice.Protobuf.Protos.User;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
|
||||
@@ -16,8 +15,18 @@ public partial class Settings : ComponentBase
|
||||
{
|
||||
try
|
||||
{
|
||||
var user = await UserContract.GetUserAsync(new());
|
||||
_request = user.Adapt<UpdateUserRequest>();
|
||||
var user = await UserContract.GetUserForCustomerAsync(new());
|
||||
_request = new UpdateUserRequest
|
||||
{
|
||||
Id = user.Id,
|
||||
FirstName = user.FirstName,
|
||||
LastName = user.LastName,
|
||||
Email = user.Email,
|
||||
NationalCode = user.NationalCode,
|
||||
EmailNotifications = user.EmailNotifications,
|
||||
SmsNotifications = user.SmsNotifications,
|
||||
PushNotifications = user.PushNotifications,
|
||||
};
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
@@ -5,10 +5,7 @@
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
||||
<MudStack Spacing="3">
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
||||
<MudText Typo="Typo.h5">شجرهنامه</MudText>
|
||||
<MudButton Variant="Variant.Text" StartIcon="@Icons.Material.Filled.ArrowBack" Href="@RouteConstants.Profile.Index">بازگشت</MudButton>
|
||||
</MudStack>
|
||||
<PageHeader Title="شجرهنامه" BackHref="@RouteConstants.Profile.Index" />
|
||||
|
||||
<MudPaper Elevation="2" Class="pa-4 rounded-lg">
|
||||
<OrganizationChart />
|
||||
|
||||
@@ -4,28 +4,25 @@
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
||||
<MudStack Spacing="3">
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
||||
<MudText Typo="Typo.h5">کیف پول</MudText>
|
||||
<MudButton Variant="Variant.Text" StartIcon="@Icons.Material.Filled.ArrowBack" Href="@RouteConstants.Profile.Index">بازگشت</MudButton>
|
||||
</MudStack>
|
||||
<PageHeader Title="کیف پول" BackHref="@RouteConstants.Profile.Index" />
|
||||
|
||||
<MudGrid Spacing="2">
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudPaper Elevation="2" Class="pa-4 rounded-lg">
|
||||
<MudText Typo="Typo.subtitle2" Class="mud-text-secondary">موجودی اعتباری</MudText>
|
||||
<MudText Typo="Typo.subtitle2" Class="mud-text-secondary">موجودی عادی</MudText>
|
||||
<MudText Typo="Typo.h4" Color="Color.Primary">@_balances.Credit</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudPaper Elevation="2" Class="pa-4 rounded-lg">
|
||||
<MudText Typo="Typo.subtitle2" Class="mud-text-secondary">موجودی فروشگاه تخفیفی</MudText>
|
||||
<MudText Typo="Typo.h4" Color="Color.Warning">@_balances.Discount</MudText>
|
||||
<MudText Typo="Typo.subtitle2" Class="mud-text-secondary">موجودی شبکه</MudText>
|
||||
<MudText Typo="Typo.h4" Color="Color.Success">@_balances.Network</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudPaper Elevation="2" Class="pa-4 rounded-lg">
|
||||
<MudText Typo="Typo.subtitle2" Class="mud-text-secondary">موجودی پاداش تیمی</MudText>
|
||||
<MudText Typo="Typo.h4" Color="Color.Success">@_balances.Network</MudText>
|
||||
<MudText Typo="Typo.subtitle2" Class="mud-text-secondary">موجودی تخفیفی</MudText>
|
||||
<MudText Typo="Typo.h4" Color="Color.Warning">@_balances.Discount</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
@@ -71,18 +68,45 @@
|
||||
</MudGrid>
|
||||
|
||||
<MudHidden Breakpoint="Breakpoint.MdAndUp" Invert="true">
|
||||
<MudTable Items="_txs" Dense="true">
|
||||
<MudTable Items="_txs" Dense="true" Hover="true" Striped="true">
|
||||
<HeaderContent>
|
||||
<MudTh>تاریخ</MudTh>
|
||||
<MudTh>مبلغ</MudTh>
|
||||
<MudTh>مسیر</MudTh>
|
||||
<MudTh>عادی (تغییرات / مانده)</MudTh>
|
||||
<MudTh>شبکه (تغییرات / مانده)</MudTh>
|
||||
<MudTh>تخفیفی (تغییرات / مانده)</MudTh>
|
||||
<MudTh>شناسه ارجاع</MudTh>
|
||||
<MudTh>توضیحات</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd>@context.Date</MudTd>
|
||||
<MudTd Color="@(context.Amount > 0 ? Color.Success : Color.Error)">@FormatPrice(context.Amount)</MudTd>
|
||||
<MudTd>@context.Channel</MudTd>
|
||||
<MudTd>@context.Description</MudTd>
|
||||
<MudTd DataLabel="تاریخ">
|
||||
<MudText Typo="Typo.caption">@context.Date</MudText>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="عادی">
|
||||
<MudStack Spacing="0">
|
||||
<MudText Color="@(context.CreditChange > 0 ? Color.Success : context.CreditChange < 0 ? Color.Error : Color.Default)" Typo="Typo.body2">
|
||||
@(context.CreditChange != 0 ? (context.CreditChange > 0 ? "+" : "") + FormatPrice(context.CreditChange) : "-")
|
||||
</MudText>
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">@FormatPrice(context.CreditBalance)</MudText>
|
||||
</MudStack>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="شبکه">
|
||||
<MudStack Spacing="0">
|
||||
<MudText Color="@(context.NetworkChange > 0 ? Color.Success : context.NetworkChange < 0 ? Color.Error : Color.Default)" Typo="Typo.body2">
|
||||
@(context.NetworkChange != 0 ? (context.NetworkChange > 0 ? "+" : "") + FormatPrice(context.NetworkChange) : "-")
|
||||
</MudText>
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">@FormatPrice(context.NetworkBalance)</MudText>
|
||||
</MudStack>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="تخفیفی">
|
||||
<MudStack Spacing="0">
|
||||
<MudText Color="@(context.DiscountChange > 0 ? Color.Success : context.DiscountChange < 0 ? Color.Error : Color.Default)" Typo="Typo.body2">
|
||||
@(context.DiscountChange != 0 ? (context.DiscountChange > 0 ? "+" : "") + FormatPrice(context.DiscountChange) : "-")
|
||||
</MudText>
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">@FormatPrice(context.DiscountBalance)</MudText>
|
||||
</MudStack>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="شناسه">@context.Channel</MudTd>
|
||||
<MudTd DataLabel="توضیحات">@context.Description</MudTd>
|
||||
</RowTemplate>
|
||||
</MudTable>
|
||||
</MudHidden>
|
||||
@@ -91,13 +115,50 @@
|
||||
<MudStack Spacing="2">
|
||||
@foreach (var tx in _txs)
|
||||
{
|
||||
<MudPaper Class="pa-3 rounded-lg" Outlined="true">
|
||||
<MudStack Spacing="1">
|
||||
<MudPaper Class="pa-3 rounded-lg" Outlined="true" Elevation="1">
|
||||
<MudStack Spacing="2">
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
||||
<MudText>@tx.Date</MudText>
|
||||
<MudText Color="@(tx.Amount > 0 ? Color.Success : Color.Error)">@FormatPrice(tx.Amount)</MudText>
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">@tx.Date</MudText>
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">شناسه: @tx.Channel</MudText>
|
||||
</MudStack>
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">@tx.Channel</MudText>
|
||||
|
||||
<MudDivider />
|
||||
|
||||
<MudStack Row="true" Spacing="2" Justify="Justify.SpaceBetween">
|
||||
<!-- کیف پول عادی -->
|
||||
<MudPaper Class="pa-2 flex-grow-1" Outlined="true">
|
||||
<MudStack Spacing="1" AlignItems="AlignItems.Center">
|
||||
<MudText Typo="Typo.caption" Color="Color.Primary">عادی</MudText>
|
||||
<MudText Color="@(tx.CreditChange > 0 ? Color.Success : tx.CreditChange < 0 ? Color.Error : Color.Default)" Style="font-weight: 600; font-size: 0.75rem;">
|
||||
@(tx.CreditChange != 0 ? (tx.CreditChange > 0 ? "+" : "") + FormatPrice(tx.CreditChange) : "-")
|
||||
</MudText>
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary" Style="font-size: 0.7rem;">@FormatPrice(tx.CreditBalance)</MudText>
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
|
||||
<!-- کیف پول شبکه -->
|
||||
<MudPaper Class="pa-2 flex-grow-1" Outlined="true">
|
||||
<MudStack Spacing="1" AlignItems="AlignItems.Center">
|
||||
<MudText Typo="Typo.caption" Color="Color.Success">شبکه</MudText>
|
||||
<MudText Color="@(tx.NetworkChange > 0 ? Color.Success : tx.NetworkChange < 0 ? Color.Error : Color.Default)" Style="font-weight: 600; font-size: 0.75rem;">
|
||||
@(tx.NetworkChange != 0 ? (tx.NetworkChange > 0 ? "+" : "") + FormatPrice(tx.NetworkChange) : "-")
|
||||
</MudText>
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary" Style="font-size: 0.7rem;">@FormatPrice(tx.NetworkBalance)</MudText>
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
|
||||
<!-- کیف پول تخفیفی -->
|
||||
<MudPaper Class="pa-2 flex-grow-1" Outlined="true">
|
||||
<MudStack Spacing="1" AlignItems="AlignItems.Center">
|
||||
<MudText Typo="Typo.caption" Color="Color.Warning">تخفیفی</MudText>
|
||||
<MudText Color="@(tx.DiscountChange > 0 ? Color.Success : tx.DiscountChange < 0 ? Color.Error : Color.Default)" Style="font-weight: 600; font-size: 0.75rem;">
|
||||
@(tx.DiscountChange != 0 ? (tx.DiscountChange > 0 ? "+" : "") + FormatPrice(tx.DiscountChange) : "-")
|
||||
</MudText>
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary" Style="font-size: 0.7rem;">@FormatPrice(tx.DiscountBalance)</MudText>
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
</MudStack>
|
||||
|
||||
<MudText Typo="Typo.body2">@tx.Description</MudText>
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
|
||||
@@ -4,10 +4,7 @@
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
||||
<MudStack Spacing="3">
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
||||
<MudText Typo="Typo.h5">درخواستهای برداشت</MudText>
|
||||
<MudButton Variant="Variant.Text" StartIcon="@Icons.Material.Filled.ArrowBack" Href="@RouteConstants.Profile.Wallet">بازگشت به کیف پول</MudButton>
|
||||
</MudStack>
|
||||
<PageHeader Title="درخواستهای برداشت" BackHref="@RouteConstants.Profile.Wallet" />
|
||||
|
||||
<!-- فرم درخواست برداشت جدید -->
|
||||
<MudPaper Elevation="2" Class="pa-4 rounded-lg">
|
||||
@@ -100,7 +97,7 @@
|
||||
|
||||
@if (_isLoading)
|
||||
{
|
||||
<MudProgressLinear Color="Color.Primary" Indeterminate="true" />
|
||||
<LoadingState />
|
||||
}
|
||||
else if (!_withdrawals.Any())
|
||||
{
|
||||
|
||||
@@ -6,10 +6,11 @@
|
||||
<MudContainer MaxWidth="MaxWidth.Large" Class="px-1">
|
||||
<MudGrid Spacing="4" Justify="Justify.Center">
|
||||
<MudItem xs="12" md="5" Class="d-none d-sm-block">
|
||||
<MudStack Spacing="2" Class="mb-6">
|
||||
<MudChip T="string" Color="Color.Secondary" Variant="Variant.Filled" Class="mb-2">ثبتنام سه
|
||||
مرحلهای</MudChip>
|
||||
<MudText Typo="Typo.h3" Class="mb-2">
|
||||
<MudStack Spacing="3" Class="mb-6">
|
||||
<MudChip T="string" Color="Color.Primary" Variant="Variant.Filled" Size="Size.Small" Class="rounded-pill">
|
||||
ثبتنام سه مرحلهای
|
||||
</MudChip>
|
||||
<MudText Typo="Typo.h3" Class="mb-1">
|
||||
فقط در چند دقیقه حساب خود را فعال کنید
|
||||
</MudText>
|
||||
<MudText Typo="Typo.body1" Class="mud-text-secondary">
|
||||
@@ -18,34 +19,47 @@
|
||||
</MudText>
|
||||
</MudStack>
|
||||
|
||||
<MudPaper Elevation="1" Class="pa-4 rounded-xl gradient-border">
|
||||
<MudStack Spacing="2">
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
|
||||
<MudIcon Icon="@Icons.Material.Filled.VerifiedUser" Color="Color.Primary" />
|
||||
<MudPaper Elevation="0" Class="pa-5 rounded-xl" Style="border:1px solid var(--mud-palette-divider);">
|
||||
<MudStack Spacing="3">
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
|
||||
<MudAvatar Size="Size.Small" Color="Color.Primary" Variant="Variant.Filled">
|
||||
<MudIcon Icon="@Icons.Material.Outlined.Verified" Size="Size.Small" />
|
||||
</MudAvatar>
|
||||
<MudText Typo="Typo.subtitle1">مزایای ثبتنام آنلاین</MudText>
|
||||
</MudStack>
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary">
|
||||
• تایید سریع و آنلاین شماره موبایل<br />
|
||||
• تکمیل اطلاعات هویتی بدون مراجعه حضوری<br />
|
||||
• دریافت نسخهی دیجیتال قرارداد برای بررسی دقیق
|
||||
</MudText>
|
||||
<MudStack Spacing="2">
|
||||
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
|
||||
<MudIcon Icon="@Icons.Material.Outlined.PhoneAndroid" Size="Size.Small" Color="Color.Primary" />
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary">تایید سریع و آنلاین شماره موبایل</MudText>
|
||||
</MudStack>
|
||||
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
|
||||
<MudIcon Icon="@Icons.Material.Outlined.Badge" Size="Size.Small" Color="Color.Primary" />
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary">تکمیل اطلاعات هویتی بدون مراجعه حضوری</MudText>
|
||||
</MudStack>
|
||||
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
|
||||
<MudIcon Icon="@Icons.Material.Outlined.Description" Size="Size.Small" Color="Color.Primary" />
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary">دریافت نسخهی دیجیتال قرارداد برای بررسی دقیق</MudText>
|
||||
</MudStack>
|
||||
</MudStack>
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12" md="7">
|
||||
<MudPaper Elevation="3" Class="pa-6 wizard-card">
|
||||
<MudPaper Elevation="1" Class="pa-5 pa-md-6 wizard-card">
|
||||
@if (_completed)
|
||||
{
|
||||
<MudStack Spacing="3" AlignItems="AlignItems.Center" Class="text-center">
|
||||
<MudAvatar Icon="@Icons.Material.Filled.CheckCircle" Color="Color.Success" Size="Size.Large" />
|
||||
<MudStack Spacing="3" AlignItems="AlignItems.Center" Class="text-center py-6">
|
||||
<MudAvatar Size="Size.Large" Color="Color.Success" Variant="Variant.Filled">
|
||||
<MudIcon Icon="@Icons.Material.Outlined.CheckCircle" Size="Size.Large" />
|
||||
</MudAvatar>
|
||||
<MudText Typo="Typo.h4">درخواست شما ثبت شد</MudText>
|
||||
<MudText Typo="Typo.body1" Class="mud-text-secondary">
|
||||
<MudText Typo="Typo.body1" Class="mud-text-secondary" Style="max-width:400px;">
|
||||
تیم ما پس از بررسی اطلاعات با شما تماس خواهد گرفت. میتوانید از طریق داشبورد وضعیت ثبتنام
|
||||
را دنبال کنید.
|
||||
</MudText>
|
||||
<MudStack Row="true" Spacing="2" Justify="Justify.Center">
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary"
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" Class="rounded-lg"
|
||||
OnClick="@(() => Navigation.NavigateTo(RouteConstants.Main.MainPage))">بازگشت به صفحه
|
||||
اصلی</MudButton>
|
||||
<MudButton Variant="Variant.Outlined" Color="Color.Primary"
|
||||
@@ -56,43 +70,45 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudStack Spacing="3" >
|
||||
<MudStack Spacing="3">
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
||||
<MudText Typo="Typo.h4">مراحل ثبتنام</MudText>
|
||||
<MudChip T="string" Color="Color.Info" Variant="Variant.Outlined" Size="Size.Small">۳ مرحله
|
||||
<MudText Typo="Typo.h5">مراحل ثبتنام</MudText>
|
||||
<MudChip T="string" Color="Color.Primary" Variant="Variant.Outlined" Size="Size.Small" Class="rounded-pill">
|
||||
مرحله @(_activeStep + 1) از ۳
|
||||
</MudChip>
|
||||
</MudStack>
|
||||
|
||||
@if (_isSubmitting)
|
||||
{
|
||||
<MudProgressLinear Color="Color.Primary" Indeterminate="true" />
|
||||
<MudProgressLinear Color="Color.Primary" Indeterminate="true" Class="rounded" />
|
||||
}
|
||||
|
||||
<MudStepper @bind-ActiveIndex="_activeStep" @ref="_mudStepper" Elevation="0" DisableClick="true" Class="mb-4 " id="registration-stepper">
|
||||
<MudStepper @bind-ActiveIndex="_activeStep" @ref="_mudStepper" Elevation="0" DisableClick="true" Class="mb-4" id="registration-stepper">
|
||||
<ChildContent>
|
||||
<MudStep Label="تأیید موبایل" Icon="@Icons.Material.Filled.Smartphone">
|
||||
<MudStep Label="تأیید موبایل" Icon="@Icons.Material.Outlined.PhoneAndroid">
|
||||
@* Inline AuthDialog with captcha enabled *@
|
||||
<AuthDialog @ref="_authDialog" InlineMode="true" HideCancelButton="true" OnLoginSuccess="@(async () => { OnPhoneVerified(); })" />
|
||||
</MudStep>
|
||||
|
||||
<MudStep Label="اطلاعات هویتی" Icon="@Icons.Material.Filled.Badge">
|
||||
<MudStep Label="اطلاعات هویتی" Icon="@Icons.Material.Outlined.Badge">
|
||||
<MudForm @ref="_stepTwoForm">
|
||||
<MudTextField Label="نام" Variant="Variant.Outlined" Immediate="true"
|
||||
@bind-Value="_model.FirstName" For="@(() => _model.FirstName)" />
|
||||
<MudTextField Label="نام خانوادگی" Variant="Variant.Outlined" Immediate="true"
|
||||
@bind-Value="_model.LastName" For="@(() => _model.LastName)" />
|
||||
<MudTextField Label="کد ملی" Variant="Variant.Outlined" Immediate="true"
|
||||
MaxLength="10" @bind-Value="_model.NationalCode"
|
||||
For="@(() => _model.NationalCode)" InputType="InputType.Number" />
|
||||
<MudStack Spacing="3">
|
||||
<MudTextField Label="نام" Variant="Variant.Outlined" Immediate="true"
|
||||
@bind-Value="_model.FirstName" For="@(() => _model.FirstName)" />
|
||||
<MudTextField Label="نام خانوادگی" Variant="Variant.Outlined" Immediate="true"
|
||||
@bind-Value="_model.LastName" For="@(() => _model.LastName)" />
|
||||
<MudTextField Label="کد ملی" Variant="Variant.Outlined" Immediate="true"
|
||||
MaxLength="10" @bind-Value="_model.NationalCode"
|
||||
For="@(() => _model.NationalCode)" InputType="InputType.Number" />
|
||||
</MudStack>
|
||||
</MudForm>
|
||||
</MudStep>
|
||||
|
||||
<MudStep Label="قوانین و قرارداد" Icon="@Icons.Material.Filled.Rule">
|
||||
<MudStep Label="قوانین و قرارداد" Icon="@Icons.Material.Outlined.Rule">
|
||||
<MudForm @ref="_stepThreeForm">
|
||||
<MudAlert Variant="Variant.Outlined" Severity="Severity.Info" Class="mb-3">
|
||||
<MudAlert Variant="Variant.Outlined" Severity="Severity.Info" Class="mb-3 rounded-lg">
|
||||
لطفاً قوانین و شرایط همکاری را با دقت مطالعه کنید و در صورت موافقت، تیک
|
||||
تایید را فعال نمایید. همچنین میتوانید نسخهی قرارداد را دانلود و ذخیره
|
||||
کنید.
|
||||
تایید را فعال نمایید.
|
||||
</MudAlert>
|
||||
<MudPaper Elevation="0" Class="terms-box pa-4 mb-3">
|
||||
<MudText Typo="Typo.subtitle2" Class="mb-2">بخشی از قوانین:</MudText>
|
||||
@@ -106,47 +122,44 @@
|
||||
</MudList>
|
||||
</MudPaper>
|
||||
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2" Class="mb-2">
|
||||
<MudButton Variant="Variant.Outlined" Color="Color.Primary"
|
||||
StartIcon="@Icons.Material.Filled.Download" Disabled="_isSubmitting"
|
||||
OnClick="DownloadContract">
|
||||
دانلود/پرینت قرارداد نمونه
|
||||
</MudButton>
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">فرمت: PDF</MudText>
|
||||
</MudStack>
|
||||
<MudButton Variant="Variant.Outlined" Color="Color.Primary" Class="rounded-lg mb-3"
|
||||
StartIcon="@Icons.Material.Outlined.Download" Disabled="_isSubmitting"
|
||||
OnClick="DownloadContract">
|
||||
دانلود قرارداد نمونه (PDF)
|
||||
</MudButton>
|
||||
|
||||
<MudCheckBox @bind-Checked="_model.AcceptTerms" Color="Color.Success"
|
||||
<MudCheckBox @bind-Checked="_model.AcceptTerms" Color="Color.Primary"
|
||||
For="@(() => _model.AcceptTerms)"
|
||||
Label="قوانین و مقررات را مطالعه کردهام و میپذیرم" />
|
||||
</MudForm>
|
||||
</MudStep>
|
||||
</ChildContent>
|
||||
<ActionContent Context="stepper">
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center" Class="mt-2">
|
||||
@if (_isAuthenticated && _activeStep == 1)
|
||||
{
|
||||
<MudButton Variant="Variant.Text" Color="Color.Secondary"
|
||||
|
||||
<MudButton Variant="Variant.Text" Color="Color.Error" Size="Size.Small"
|
||||
OnClick="@AuthService.LogoutAsync">خروج از حساب</MudButton>
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
<MudButton Variant="Variant.Text" Color="Color.Secondary"
|
||||
Disabled="_activeStep == 0 || _isSubmitting"
|
||||
OnClick="@(async () => { await GoBack(); })">مرحله قبل</MudButton>
|
||||
<MudButton Variant="Variant.Text" Color="Color.Secondary" Size="Size.Small"
|
||||
Disabled="_activeStep == 0 || _isSubmitting"
|
||||
OnClick="@(async () => { await GoBack(); })">مرحله قبل</MudButton>
|
||||
}
|
||||
<MudSpacer/>
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary"
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" Class="rounded-lg"
|
||||
Disabled="_isSubmitting"
|
||||
OnClick="@(async () => { await GoNextAsync(); })">
|
||||
@if (_isSubmitting)
|
||||
{
|
||||
<MudProgressCircular Size="Size.Small" Indeterminate="true" Class="me-2" />
|
||||
}
|
||||
@_nextButtonText
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
</ActionContent>
|
||||
</MudStepper>
|
||||
|
||||
|
||||
</MudStack>
|
||||
}
|
||||
</MudPaper>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using DateTimeConverterCL;
|
||||
using FrontOffice.BFF.User.Protobuf.Protos.User;
|
||||
using CMSMicroservice.Protobuf.Protos.User;
|
||||
using FrontOffice.Main.Shared;
|
||||
using FrontOffice.Main.Utilities;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
@@ -23,13 +23,14 @@ public partial class RegisterWizard
|
||||
private bool _completed;
|
||||
private AuthDialog? _authDialog;
|
||||
private MudStepper? _mudStepper;
|
||||
private UpdateUserRequest _updateUserRequest = new();
|
||||
private UpdateCustomerProfileRequest _updateUserRequest = new();
|
||||
private bool _isAuthenticated;
|
||||
private CancellationTokenSource? _operationCts;
|
||||
private UserAuthInfo _userAuthInfo;
|
||||
private Guid signGuid = Guid.NewGuid();
|
||||
private const string TokenStorageKey = "auth:token";
|
||||
private readonly DialogOptions _normalWidth = new() { MaxWidth = MaxWidth.ExtraSmall, FullWidth = true };
|
||||
private bool _initialDataLoaded;
|
||||
|
||||
[Inject] private AuthService AuthService { get; set; } = default!;
|
||||
[Inject] private IDeviceDetector _deviceDetector { get; set; } = default!;
|
||||
@@ -55,6 +56,7 @@ public partial class RegisterWizard
|
||||
if (_userAuthInfo.IsSignMainContract)
|
||||
{
|
||||
Navigation.NavigateTo(RouteConstants.Profile.Index);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_activeStep == 0)
|
||||
@@ -63,11 +65,12 @@ public partial class RegisterWizard
|
||||
_activeStep = 1;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
else if (_activeStep == 1)
|
||||
else if (_activeStep == 1 && !_initialDataLoaded)
|
||||
{
|
||||
_initialDataLoaded = true;
|
||||
try
|
||||
{
|
||||
var existUser = await UserContract.GetUserAsync(new Empty());
|
||||
var existUser = await UserContract.GetUserForCustomerAsync(new GetUserForCustomerRequest());
|
||||
if (existUser != null && !string.IsNullOrEmpty(existUser.FirstName) &&
|
||||
string.IsNullOrWhiteSpace(_model.FirstName))
|
||||
{
|
||||
@@ -95,7 +98,6 @@ public partial class RegisterWizard
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
await base.OnAfterRenderAsync(firstRender);
|
||||
}
|
||||
|
||||
@@ -355,7 +357,7 @@ public partial class RegisterWizard
|
||||
_updateUserRequest.NationalCode = _model.NationalCode.PersianToEnglish();
|
||||
|
||||
|
||||
await UserContract.UpdateUserAsync(request: _updateUserRequest);
|
||||
await UserContract.UpdateCustomerProfileAsync(request: _updateUserRequest);
|
||||
Snackbar.Add("اطلاعات شخصی با موفقیت ذخیره شد.", Severity.Success);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
||||
<MudStack Spacing="3">
|
||||
<MudText Typo="Typo.h4">سبد خرید</MudText>
|
||||
<PageHeader Title="سبد خرید" BackHref="@RouteConstants.Store.Products" />
|
||||
|
||||
@if (CartData.Items.Count == 0)
|
||||
{
|
||||
@@ -30,8 +30,8 @@
|
||||
<MudTd>
|
||||
<MudStack Spacing="1">
|
||||
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
|
||||
<MudImage Src="@GetProductImageUrl(context.ImageUrl)" Alt="@context.Title"
|
||||
Width="64" Height="64" Class="product-thumb" />
|
||||
<AppImage Path="@GetProductImageUrl(context.ImageUrl)" Alt="@context.Title"
|
||||
ImgWidth="64" ImgHeight="64" Class="product-thumb" />
|
||||
<MudText>@context.Title</MudText>
|
||||
</MudStack>
|
||||
@if (context.Discount > 0 || !string.IsNullOrWhiteSpace(context.Created) || !string.IsNullOrWhiteSpace(context.Description))
|
||||
@@ -86,8 +86,8 @@
|
||||
<MudStack Spacing="1">
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
||||
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
|
||||
<MudImage Src="@GetProductImageUrl(item.ImageUrl)" Alt="@item.Title"
|
||||
Width="50" Height="50" Class="rounded-circle" />
|
||||
<AppImage Path="@GetProductImageUrl(item.ImageUrl)" Alt="@item.Title"
|
||||
ImgWidth="50" ImgHeight="50" Class="rounded-circle" />
|
||||
<MudText Typo="Typo.subtitle2">@item.Title</MudText>
|
||||
</MudStack>
|
||||
@if (item.Discount > 0)
|
||||
@@ -154,8 +154,8 @@
|
||||
</MudStack>
|
||||
}
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
||||
<MudText Typo="Typo.h6" Style="font-weight:bold;">مبلغ قابل پرداخت:</MudText>
|
||||
<MudText Typo="Typo.h6" Color="Color.Primary" Style="font-weight:bold;">@FormatPrice(TotalWithVAT) تومان</MudText>
|
||||
<MudText Typo="Typo.h6" Class="fw-bold">مبلغ قابل پرداخت:</MudText>
|
||||
<MudText Typo="Typo.h6" Color="Color.Primary" Class="fw-bold">@FormatPrice(TotalWithVAT) تومان</MudText>
|
||||
</MudStack>
|
||||
</MudStack>
|
||||
|
||||
@@ -185,8 +185,8 @@
|
||||
}
|
||||
<MudDivider Class="my-1" />
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||||
<MudText Typo="Typo.subtitle1" Style="font-weight:bold;">مبلغ قابل پرداخت:</MudText>
|
||||
<MudText Typo="Typo.subtitle1" Color="Color.Primary" Style="font-weight:bold;">@FormatPrice(TotalWithVAT) تومان</MudText>
|
||||
<MudText Typo="Typo.subtitle1" Class="fw-bold">مبلغ قابل پرداخت:</MudText>
|
||||
<MudText Typo="Typo.subtitle1" Color="Color.Primary" Class="fw-bold">@FormatPrice(TotalWithVAT) تومان</MudText>
|
||||
</MudStack>
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
|
||||
@@ -66,7 +66,7 @@ public partial class Cart : ComponentBase, IDisposable
|
||||
private long VATAmount => VAT.CalculateVAT(CartData.Total);
|
||||
|
||||
private static string GetProductImageUrl(string? imageUrl)
|
||||
=> string.IsNullOrWhiteSpace(imageUrl) ? "/images/product-placeholder.svg" : imageUrl;
|
||||
=> string.IsNullOrWhiteSpace(imageUrl) ? "/images/product-placeholder.svg" : imageUrl.TrimStart('/');
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
<PageTitle>دستهبندیها</PageTitle>
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
||||
<PageHeader Title="دستهبندی محصولات" BackHref="@RouteConstants.Store.Products" />
|
||||
<MudPaper Elevation="1" Class="pa-4 mb-4 rounded-lg">
|
||||
<MudStack Spacing="1">
|
||||
<MudText Typo="Typo.h5">دستهبندی محصولات</MudText>
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary">
|
||||
از بین درخت دستهبندیها انتخاب کنید تا محصولات آن دسته را مشاهده نمایید.
|
||||
</MudText>
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
@using FrontOffice.BFF.UserOrder.Protobuf.Protos.UserOrder
|
||||
@using CMSMicroservice.Protobuf.Protos.UserOrder
|
||||
@using CMSMicroservice.Protobuf.Protos
|
||||
@attribute [Route(RouteConstants.Store.CheckoutSummary)]
|
||||
|
||||
<PageTitle>خلاصه خرید</PageTitle>
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
||||
<PageHeader Title="خلاصه خرید" BackHref="@RouteConstants.Store.Cart" />
|
||||
<MudGrid Spacing="3">
|
||||
<MudItem xs="12" md="8">
|
||||
<MudPaper Elevation="2" Class="pa-4 rounded-lg mb-3">
|
||||
@@ -29,7 +31,7 @@
|
||||
@foreach (var address in _addresses)
|
||||
{
|
||||
<MudPaper Outlined="@(_selectedAddress?.Id != address.Id)"
|
||||
Elevation="@(_selectedAddress?.Id == address.Id ? 4 : 0)"
|
||||
Elevation="@(_selectedAddress?.Id == address.Id ? 2 : 0)"
|
||||
Class="pa-3 rounded-xl cursor-pointer"
|
||||
Style="@(_selectedAddress?.Id == address.Id ? "border: 2px solid var(--mud-palette-primary);" : "")"
|
||||
@onclick="() => _selectedAddress = address">
|
||||
@@ -83,8 +85,8 @@
|
||||
<MudListItemText>
|
||||
<MudStack Spacing="1">
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
||||
<MudImage Src="@GetProductImageUrl(item.ImageUrl)" Alt="@item.Title"
|
||||
Width="48" Height="48" Class="rounded-circle" />
|
||||
<AppImage Path="@GetProductImageUrl(item.ImageUrl)" Alt="@item.Title"
|
||||
ImgWidth="48" ImgHeight="48" Class="rounded-circle" />
|
||||
<MudText Typo="Typo.subtitle2">@item.Title</MudText>
|
||||
<MudText Typo="Typo.subtitle2">@FormatPrice(item.LineTotal)</MudText>
|
||||
</MudStack>
|
||||
@@ -117,8 +119,8 @@
|
||||
}
|
||||
<MudDivider Class="my-1" />
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||||
<MudText Typo="Typo.subtitle1" Style="font-weight: bold;">مبلغ قابل پرداخت:</MudText>
|
||||
<MudText Typo="Typo.subtitle1" Color="Color.Primary" Style="font-weight: bold;">@FormatPrice(VAT.AddVAT(Cart.Total))</MudText>
|
||||
<MudText Typo="Typo.subtitle1" Class="fw-bold">مبلغ قابل پرداخت:</MudText>
|
||||
<MudText Typo="Typo.subtitle1" Color="Color.Primary" Class="fw-bold">@FormatPrice(VAT.AddVAT(Cart.Total))</MudText>
|
||||
</MudStack>
|
||||
</MudStack>
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
using FrontOffice.BFF.UserAddress.Protobuf.Protos.UserAddress;
|
||||
using FrontOffice.BFF.UserOrder.Protobuf.Protos.UserOrder;
|
||||
using CMSMicroservice.Protobuf.Protos.UserAddress;
|
||||
using CMSMicroservice.Protobuf.Protos.UserOrder;
|
||||
using FrontOffice.Main.Utilities;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
using Messages = CMSMicroservice.Protobuf.Protos;
|
||||
|
||||
namespace FrontOffice.Main.Pages.Store;
|
||||
|
||||
@@ -15,12 +16,12 @@ public partial class CheckoutSummary : ComponentBase
|
||||
[Inject] private UserOrderContract.UserOrderContractClient UserOrderContract { get; set; } = default!;
|
||||
// Snackbar and Navigation are injected via _Imports.razor
|
||||
|
||||
private List<GetAllUserAddressByFilterResponseModel> _addresses = new();
|
||||
private GetAllUserAddressByFilterResponseModel? _selectedAddress;
|
||||
private List<CustomerAddressModel> _addresses = new();
|
||||
private CustomerAddressModel? _selectedAddress;
|
||||
private bool _loadingAddresses;
|
||||
private long walletBalance;
|
||||
|
||||
private PaymentMethod _payment = PaymentMethod.Wallet;
|
||||
private Messages.PaymentMethod _payment = Messages.PaymentMethod.Wallet;
|
||||
|
||||
private bool CanPlaceOrder => Cart.Items.Count > 0 && _selectedAddress != null;
|
||||
|
||||
@@ -55,7 +56,7 @@ public partial class CheckoutSummary : ComponentBase
|
||||
_loadingAddresses = true;
|
||||
try
|
||||
{
|
||||
var response = await UserAddressContract.GetAllUserAddressByFilterAsync(new());
|
||||
var response = await UserAddressContract.GetCustomerAddressesAsync(new());
|
||||
if (response?.Models?.Any() == true)
|
||||
{
|
||||
_addresses = response.Models.ToList();
|
||||
@@ -112,5 +113,5 @@ public partial class CheckoutSummary : ComponentBase
|
||||
private long CalculateVAT() => VAT.CalculateVAT(Cart.Total);
|
||||
|
||||
private static string GetProductImageUrl(string? imageUrl)
|
||||
=> string.IsNullOrWhiteSpace(imageUrl) ? "/images/product-placeholder.svg" : imageUrl;
|
||||
=> string.IsNullOrWhiteSpace(imageUrl) ? "/images/product-placeholder.svg" : imageUrl.TrimStart('/');
|
||||
}
|
||||
@@ -6,27 +6,33 @@
|
||||
@if (_loading)
|
||||
{
|
||||
<MudContainer MaxWidth="MaxWidth.Medium" Class="py-6">
|
||||
<MudStack AlignItems="AlignItems.Center">
|
||||
<MudProgressCircular Indeterminate="true" Color="Color.Primary" />
|
||||
</MudStack>
|
||||
<LoadingState />
|
||||
</MudContainer>
|
||||
}
|
||||
else if (_order is null)
|
||||
{
|
||||
<MudContainer MaxWidth="MaxWidth.Medium" Class="py-6">
|
||||
<MudAlert Severity="Severity.Warning">سفارش یافت نشد.</MudAlert>
|
||||
<MudButton Variant="Variant.Text" StartIcon="@Icons.Material.Filled.ArrowBack" OnClick="() => Navigation.NavigateTo(RouteConstants.Store.Orders)">بازگشت</MudButton>
|
||||
<EmptyState Icon="@Icons.Material.Filled.SearchOff"
|
||||
Title="سفارش یافت نشد."
|
||||
ActionText="بازگشت"
|
||||
ActionHref="@RouteConstants.Store.Orders" />
|
||||
</MudContainer>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
||||
<PageHeader Title="@($"جزئیات سفارش #{_order.Id}")" BackHref="@RouteConstants.Store.Orders" />
|
||||
<MudPaper Elevation="2" Class="pa-4 rounded-lg">
|
||||
<MudStack Spacing="2">
|
||||
<MudText Typo="Typo.h5">سفارش #@_order.Id</MudText>
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary">تاریخ: @_order.PaymentDate.ToDateTime().MiladiToJalaliWithTime()</MudText>
|
||||
@if (_order.PaymentDate != null)
|
||||
{
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary">تاریخ پرداخت: @_order.PaymentDate.ToDateTime().MiladiToJalaliWithTime()</MudText>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary">تاریخ ثبت: @DateTime.Now.MiladiToJalaliWithTime()</MudText>
|
||||
}
|
||||
<MudText Typo="Typo.body2">وضعیت: @GetStatusText(_order.PaymentStatus)</MudText>
|
||||
<MudText Typo="Typo.body2">روش پرداخت: @GetPaymentMethodText(_order.PaymentMethod)</MudText>
|
||||
<MudText Typo="Typo.body2">آدرس: @_order.UserAddressText</MudText>
|
||||
<MudDivider Class="my-2" />
|
||||
<MudHidden Breakpoint="Breakpoint.MdAndUp" Invert="true">
|
||||
@@ -40,14 +46,14 @@ else
|
||||
<RowTemplate>
|
||||
<MudTd>
|
||||
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
|
||||
<MudImage Src="@GetProductImageUrl(context.ProductThumbnailPath)" Alt="@context.ProductTitle"
|
||||
Width="60" Height="60" Class="rounded-circle" />
|
||||
<MudText>@context.ProductThumbnailPath</MudText>
|
||||
<AppImage Path="@GetProductImageUrl(context.ProductThumbnailPath)" Alt="@context.ProductTitle"
|
||||
ImgWidth="60" ImgHeight="60" Class="rounded-circle" />
|
||||
<MudText>@context.ProductTitle</MudText>
|
||||
</MudStack>
|
||||
</MudTd>
|
||||
<MudTd>@FormatPrice(context.UnitPrice.Value)</MudTd>
|
||||
<MudTd>@FormatPrice(context.UnitPrice ?? 0)</MudTd>
|
||||
<MudTd>@context.Count</MudTd>
|
||||
<MudTd>@FormatPrice(context.UnitPrice.Value*context.Count.Value)</MudTd>
|
||||
<MudTd>@FormatPrice((context.UnitPrice ?? 0) * (context.Count ?? 0))</MudTd>
|
||||
</RowTemplate>
|
||||
</MudTable>
|
||||
</MudHidden>
|
||||
@@ -59,15 +65,15 @@ else
|
||||
<MudPaper Class="pa-3 rounded-lg" Outlined="true">
|
||||
<MudStack Spacing="1">
|
||||
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
|
||||
<MudImage Src="@GetProductImageUrl(it.ProductThumbnailPath)" Alt="@it.ProductTitle"
|
||||
Width="60" Height="60" Class="rounded-circle" />
|
||||
<AppImage Path="@GetProductImageUrl(it.ProductThumbnailPath)" Alt="@it.ProductTitle"
|
||||
ImgWidth="60" ImgHeight="60" Class="rounded-circle" />
|
||||
<MudText>@it.ProductTitle</MudText>
|
||||
</MudStack>
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||||
<MudText Class="mud-text-secondary">@FormatPrice(it.UnitPrice.Value) واحد</MudText>
|
||||
<MudText Class="mud-text-secondary">@FormatPrice(it.UnitPrice ?? 0) واحد</MudText>
|
||||
<MudText>تعداد: @it.Count</MudText>
|
||||
</MudStack>
|
||||
<MudText>جمع: @FormatPrice(it.UnitPrice.Value*it.Count.Value)</MudText>
|
||||
<MudText>جمع: @FormatPrice((it.UnitPrice ?? 0) * (it.Count ?? 0))</MudText>
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
}
|
||||
@@ -77,7 +83,7 @@ else
|
||||
|
||||
@* نمایش جزئیات مالی *@
|
||||
@{
|
||||
var subtotal = _order.FactorDetails.Sum(s => s.UnitPrice.Value * s.Count.Value);
|
||||
var subtotal = _order.FactorDetails.Sum(s => (s.UnitPrice ?? 0) * (s.Count ?? 0));
|
||||
}
|
||||
<MudStack Spacing="1" Class="pa-2" Style="background-color: var(--mud-palette-background-grey); border-radius: 8px;">
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||||
@@ -94,15 +100,15 @@ else
|
||||
</MudStack>
|
||||
<MudDivider Class="my-1" />
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||||
<MudText Typo="Typo.subtitle1" Style="font-weight: bold;">مبلغ قابل پرداخت:</MudText>
|
||||
<MudText Typo="Typo.subtitle1" Color="Color.Primary" Style="font-weight: bold;">@FormatPrice(_order.VatInfo.TotalAmount)</MudText>
|
||||
<MudText Typo="Typo.subtitle1" Class="fw-bold">مبلغ قابل پرداخت:</MudText>
|
||||
<MudText Typo="Typo.subtitle1" Color="Color.Primary" Class="fw-bold">@FormatPrice(_order.VatInfo.TotalAmount)</MudText>
|
||||
</MudStack>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||||
<MudText Typo="Typo.subtitle1" Style="font-weight: bold;">مبلغ قابل پرداخت:</MudText>
|
||||
<MudText Typo="Typo.subtitle1" Color="Color.Primary" Style="font-weight: bold;">@FormatPrice(subtotal)</MudText>
|
||||
<MudText Typo="Typo.subtitle1" Class="fw-bold">مبلغ قابل پرداخت:</MudText>
|
||||
<MudText Typo="Typo.subtitle1" Color="Color.Primary" Class="fw-bold">@FormatPrice(subtotal)</MudText>
|
||||
</MudStack>
|
||||
}
|
||||
</MudStack>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using FrontOffice.BFF.UserOrder.Protobuf.Protos.UserOrder;
|
||||
using CMSMicroservice.Protobuf.Protos.UserOrder;
|
||||
using FrontOffice.Main.Utilities;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Messages = CMSMicroservice.Protobuf.Protos;
|
||||
|
||||
namespace FrontOffice.Main.Pages.Store;
|
||||
|
||||
@@ -22,28 +23,28 @@ public partial class OrderDetail : ComponentBase
|
||||
|
||||
private static string FormatPrice(long price) => string.Format("{0:N0} تومان", price);
|
||||
|
||||
private string GetStatusText(PaymentStatus orderPaymentStatus)
|
||||
private string GetStatusText(Messages.PaymentStatus orderPaymentStatus)
|
||||
{
|
||||
return orderPaymentStatus switch
|
||||
{
|
||||
PaymentStatus.Pending => "در انتظار پرداخت",
|
||||
PaymentStatus.Success => "پرداخت شده",
|
||||
PaymentStatus.Reject => "پرداخت ناموفق",
|
||||
Messages.PaymentStatus.Pending => "در انتظار پرداخت",
|
||||
Messages.PaymentStatus.Success => "پرداخت شده",
|
||||
Messages.PaymentStatus.Reject => "پرداخت ناموفق",
|
||||
_ => "نامشخص",
|
||||
};
|
||||
}
|
||||
|
||||
private string GetPaymentMethodText(PaymentMethod orderPaymentMethod)
|
||||
private string GetPaymentMethodText(Messages.PaymentMethod orderPaymentMethod)
|
||||
{
|
||||
return orderPaymentMethod switch
|
||||
{
|
||||
PaymentMethod.Wallet => "کیف پول",
|
||||
PaymentMethod.Ipg => "درگاه پرداخت اینترنتی",
|
||||
Messages.PaymentMethod.Wallet => "کیف پول",
|
||||
Messages.PaymentMethod.Ipg => "درگاه پرداخت اینترنتی",
|
||||
_ => "نامشخص",
|
||||
};
|
||||
}
|
||||
|
||||
private static string GetProductImageUrl(string? imageUrl)
|
||||
=> string.IsNullOrWhiteSpace(imageUrl) ? "/images/product-placeholder.svg" : UrlUtility.DownloadUrl+imageUrl;
|
||||
=> string.IsNullOrWhiteSpace(imageUrl) ? "/images/product-placeholder.svg" : imageUrl.TrimStart('/');
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
@using FrontOffice.BFF.UserOrder.Protobuf.Protos.UserOrder
|
||||
@using CMSMicroservice.Protobuf.Protos.UserOrder
|
||||
@attribute [Route(RouteConstants.Store.OrderTracking + "{id:long}")]
|
||||
|
||||
<PageTitle>پیگیری سفارش</PageTitle>
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.Medium" Class="py-6">
|
||||
<PageHeader Title="پیگیری سفارش" BackHref="@RouteConstants.Store.Orders" />
|
||||
@if (_loading)
|
||||
{
|
||||
<MudStack AlignItems="AlignItems.Center">
|
||||
@@ -15,7 +16,7 @@
|
||||
{
|
||||
<MudAlert Severity="Severity.Warning">سفارش یافت نشد.</MudAlert>
|
||||
<MudButton Variant="Variant.Text" StartIcon="@Icons.Material.Filled.ArrowBack"
|
||||
Href="@RouteConstants.Store.Orders">
|
||||
OnClick="GoBack">
|
||||
بازگشت به سفارشها
|
||||
</MudButton>
|
||||
}
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
using FrontOffice.BFF.UserOrder.Protobuf.Protos.UserOrder;
|
||||
using CMSMicroservice.Protobuf.Protos.UserOrder;
|
||||
using FrontOffice.Main.Utilities;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.JSInterop;
|
||||
using Messages = CMSMicroservice.Protobuf.Protos;
|
||||
|
||||
namespace FrontOffice.Main.Pages.Store;
|
||||
|
||||
public partial class OrderTracking : ComponentBase
|
||||
{
|
||||
[Inject] private OrderService OrderService { get; set; } = default!;
|
||||
[Inject] private IJSRuntime JS { get; set; } = default!;
|
||||
|
||||
[Parameter] public long id { get; set; }
|
||||
|
||||
@@ -45,7 +48,7 @@ public partial class OrderTracking : ComponentBase
|
||||
if (_order is null) return;
|
||||
|
||||
// Build tracking steps based on PaymentStatus
|
||||
var isPaid = _order.PaymentStatus == PaymentStatus.Success;
|
||||
var isPaid = _order.PaymentStatus == Messages.PaymentStatus.Success;
|
||||
|
||||
_trackingSteps = new List<TrackingStep>
|
||||
{
|
||||
@@ -107,10 +110,10 @@ public partial class OrderTracking : ComponentBase
|
||||
return $"{persianCalendar.GetYear(date)}/{persianCalendar.GetMonth(date):00}/{persianCalendar.GetDayOfMonth(date):00}";
|
||||
}
|
||||
|
||||
private static string GetPaymentMethodText(PaymentMethod method) => method switch
|
||||
private static string GetPaymentMethodText(Messages.PaymentMethod method) => method switch
|
||||
{
|
||||
PaymentMethod.Ipg => "پرداخت آنلاین",
|
||||
PaymentMethod.Wallet => "کیف پول",
|
||||
Messages.PaymentMethod.Ipg => "پرداخت آنلاین",
|
||||
Messages.PaymentMethod.Wallet => "کیف پول",
|
||||
_ => "نامشخص"
|
||||
};
|
||||
|
||||
@@ -122,4 +125,6 @@ public partial class OrderTracking : ComponentBase
|
||||
public bool IsCurrent { get; set; }
|
||||
public string Date { get; set; } = "";
|
||||
}
|
||||
|
||||
private async Task GoBack() => await JS.InvokeVoidAsync("history.back");
|
||||
}
|
||||
|
||||
@@ -4,12 +4,10 @@
|
||||
<PageTitle>سفارشات من</PageTitle>
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
||||
<MudText Typo="Typo.h4" Class="mb-3">سفارشات من</MudText>
|
||||
<PageHeader Title="سفارشات من" BackHref="@RouteConstants.Store.Products" />
|
||||
@if (_loading)
|
||||
{
|
||||
<MudStack AlignItems="AlignItems.Center" Class="py-4">
|
||||
<MudProgressCircular Indeterminate="true" Color="Color.Primary" />
|
||||
</MudStack>
|
||||
<LoadingState />
|
||||
}
|
||||
else if (_orders.Count == 0)
|
||||
{
|
||||
@@ -28,9 +26,18 @@
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd>@context.Id</MudTd>
|
||||
<MudTd>@context.PaymentDate.ToDateTime().MiladiToJalaliWithTime()</MudTd>
|
||||
<MudTd>
|
||||
@if (context.PaymentDate != null)
|
||||
{
|
||||
@context.PaymentDate.ToDateTime().MiladiToJalaliWithTime()
|
||||
}
|
||||
else
|
||||
{
|
||||
<text>در انتظار پرداخت</text>
|
||||
}
|
||||
</MudTd>
|
||||
<MudTd>@GetStatusText(context.PaymentStatus)</MudTd>
|
||||
<MudTd>@FormatPrice(context.FactorDetails.Sum(s=>s.UnitPrice.Value*s.Count.Value))</MudTd>
|
||||
<MudTd>@FormatPrice(context.FactorDetails.Sum(s=>(s.UnitPrice ?? 0) * (s.Count ?? 0)))</MudTd>
|
||||
<MudTd>
|
||||
<MudButton Variant="Variant.Text" Href="@($"{RouteConstants.Store.OrderDetail}{context.Id}")" StartIcon="@Icons.Material.Filled.Receipt">جزئیات</MudButton>
|
||||
</MudTd>
|
||||
@@ -46,11 +53,20 @@
|
||||
<MudStack Spacing="1">
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||||
<MudText>سفارش #@o.Id</MudText>
|
||||
<MudText Class="mud-text-secondary">@o.PaymentDate.ToDateTime().MiladiToJalaliWithTime()</MudText>
|
||||
<MudText Class="mud-text-secondary">
|
||||
@if (o.PaymentDate != null)
|
||||
{
|
||||
@o.PaymentDate.ToDateTime().MiladiToJalaliWithTime()
|
||||
}
|
||||
else
|
||||
{
|
||||
<text>در انتظار پرداخت</text>
|
||||
}
|
||||
</MudText>
|
||||
</MudStack>
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||||
<MudText>وضعیت: @GetStatusText(o.PaymentStatus)</MudText>
|
||||
<MudText Color="Color.Primary">@FormatPrice(o.FactorDetails.Sum(s=>s.UnitPrice.Value*s.Count.Value))</MudText>
|
||||
<MudText Color="Color.Primary">@FormatPrice(o.FactorDetails.Sum(s=>(s.UnitPrice ?? 0) * (s.Count ?? 0)))</MudText>
|
||||
</MudStack>
|
||||
<MudStack Row="true" Justify="Justify.FlexEnd">
|
||||
<MudButton Size="Size.Small" Variant="Variant.Outlined" Href="@($"{RouteConstants.Store.OrderDetail}{o.Id}")" StartIcon="@Icons.Material.Filled.Receipt">جزئیات</MudButton>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
using System;
|
||||
using FrontOffice.BFF.UserOrder.Protobuf.Protos.UserOrder;
|
||||
using CMSMicroservice.Protobuf.Protos.UserOrder;
|
||||
using FrontOffice.Main.Utilities;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Messages = CMSMicroservice.Protobuf.Protos;
|
||||
using MudBlazor;
|
||||
|
||||
namespace FrontOffice.Main.Pages.Store;
|
||||
@@ -45,13 +46,13 @@ public partial class Orders : ComponentBase
|
||||
private static string FormatPrice(long price) => string.Format("{0:N0} تومان", price);
|
||||
|
||||
|
||||
private string GetStatusText(PaymentStatus contextPaymentStatus)
|
||||
private string GetStatusText(Messages.PaymentStatus contextPaymentStatus)
|
||||
{
|
||||
return contextPaymentStatus switch
|
||||
{
|
||||
PaymentStatus.Pending => "در انتظار پرداخت",
|
||||
PaymentStatus.Success => "پرداخت شده",
|
||||
PaymentStatus.Reject => "پرداخت ناموفق",
|
||||
Messages.PaymentStatus.Pending => "در انتظار پرداخت",
|
||||
Messages.PaymentStatus.Success => "پرداخت شده",
|
||||
Messages.PaymentStatus.Reject => "پرداخت ناموفق",
|
||||
_ => "نامشخص",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -6,23 +6,22 @@
|
||||
@if (_loading)
|
||||
{
|
||||
<MudContainer MaxWidth="MaxWidth.Medium" Class="py-6">
|
||||
<MudStack AlignItems="AlignItems.Center">
|
||||
<MudProgressCircular Color="Color.Primary" Indeterminate="true"/>
|
||||
</MudStack>
|
||||
<LoadingState />
|
||||
</MudContainer>
|
||||
}
|
||||
else if (_product is null)
|
||||
{
|
||||
<MudContainer MaxWidth="MaxWidth.Medium" Class="py-6">
|
||||
<MudAlert Severity="Severity.Warning">محصول یافت نشد.</MudAlert>
|
||||
<MudButton Class="mt-2" Variant="Variant.Text" StartIcon="@Icons.Material.Filled.ArrowBack"
|
||||
OnClick="() => Navigation.NavigateTo(RouteConstants.Store.Products)">بازگشت
|
||||
</MudButton>
|
||||
<EmptyState Icon="@Icons.Material.Filled.SearchOff"
|
||||
Title="محصول یافت نشد."
|
||||
ActionText="بازگشت"
|
||||
ActionHref="@RouteConstants.Store.Products" />
|
||||
</MudContainer>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
||||
<PageHeader Title="جزئیات محصول" BackHref="@RouteConstants.Store.Products" />
|
||||
<MudGrid Spacing="3">
|
||||
<MudItem xs="12" md="6">
|
||||
@if (_product is not null)
|
||||
@@ -32,7 +31,9 @@ else
|
||||
<MudPaper Class="rounded-lg pa-2">
|
||||
@if (!string.IsNullOrWhiteSpace(MainImageUrl))
|
||||
{
|
||||
<div class="rounded-lg" style="width:100%;height:360px;background-image: url('@MainImageUrl');background-size: cover; background-position: center;border-radius: 0.5rem;color: transparent;" >@MainImageAlt</div>
|
||||
<AppImage Path="@MainImageUrl" Alt="@MainImageAlt"
|
||||
ObjectFit="ObjectFit.Cover"
|
||||
Style="width:100%; max-height:400px; border-radius:12px;" />
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -45,7 +46,7 @@ else
|
||||
@foreach (var item in _galleryItems)
|
||||
{
|
||||
<MudButton Variant="Variant.Text" Class="p-0" Style="min-width:0;" OnClick="() => SelectGalleryImage(item)">
|
||||
<MudImage Src="@GetThumbnailUrl(item)" Alt="@item.Title" Class="rounded-sm" Style="@GetThumbnailStyle(item)" />
|
||||
<AppImage Path="@GetThumbnailUrl(item)" Alt="@item.Title" Class="rounded-sm" Style="@GetThumbnailStyle(item)" />
|
||||
</MudButton>
|
||||
}
|
||||
</MudStack>
|
||||
|
||||
@@ -3,7 +3,18 @@
|
||||
|
||||
<PageTitle>محصولات</PageTitle>
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.Large" Class="pa-2 pa-md-6">
|
||||
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
||||
<!-- Hero Banner -->
|
||||
<MudPaper Class="store-hero pa-6 pa-md-8 mb-4 rounded-xl" Elevation="0">
|
||||
<MudStack AlignItems="AlignItems.Center" Spacing="2">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Storefront" Size="Size.Large" Class="dash-hero-name" />
|
||||
<MudText Typo="Typo.h5" Align="Align.Center" Class="dash-hero-name">فروشگاه کارا بازار سلامت</MudText>
|
||||
<MudText Typo="Typo.body2" Align="Align.Center" Class="dash-hero-sub">
|
||||
محصولات با کیفیت با ضمانت اصالت و ارسال سریع
|
||||
</MudText>
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
|
||||
<MudPaper Elevation="1" Class="pa-3 pa-md-4 mb-4 rounded-lg">
|
||||
<MudGrid Spacing="2" AlignItems="AlignItems.Center">
|
||||
@* ردیف اول: جستجو و دکمهها *@
|
||||
@@ -44,25 +55,25 @@
|
||||
Margin="Margin.Dense"
|
||||
AnchorOrigin="Origin.BottomCenter"
|
||||
AdornmentIcon="@Icons.Material.Filled.Sort">
|
||||
<MudSelectItem Value="ProductSortOption.PriceDesc">
|
||||
<MudSelectItem T="ProductSortOption" Value="ProductSortOption.PriceDesc">
|
||||
<div class="d-flex align-center gap-2">
|
||||
<MudIcon Icon="@Icons.Material.Filled.ArrowDownward" Size="Size.Small"/>
|
||||
گرانترین
|
||||
</div>
|
||||
</MudSelectItem>
|
||||
<MudSelectItem Value="ProductSortOption.PriceAsc">
|
||||
<MudSelectItem T="ProductSortOption" Value="ProductSortOption.PriceAsc">
|
||||
<div class="d-flex align-center gap-2">
|
||||
<MudIcon Icon="@Icons.Material.Filled.ArrowUpward" Size="Size.Small"/>
|
||||
ارزانترین
|
||||
</div>
|
||||
</MudSelectItem>
|
||||
<MudSelectItem Value="ProductSortOption.Newest">
|
||||
<MudSelectItem T="ProductSortOption" Value="ProductSortOption.Newest">
|
||||
<div class="d-flex align-center gap-2">
|
||||
<MudIcon Icon="@Icons.Material.Filled.NewReleases" Size="Size.Small"/>
|
||||
جدیدترین
|
||||
</div>
|
||||
</MudSelectItem>
|
||||
<MudSelectItem Value="ProductSortOption.Title">
|
||||
<MudSelectItem T="ProductSortOption" Value="ProductSortOption.Title">
|
||||
<div class="d-flex align-center gap-2">
|
||||
<MudIcon Icon="@Icons.Material.Filled.SortByAlpha" Size="Size.Small"/>
|
||||
الفبایی
|
||||
|
||||
@@ -8,15 +8,19 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="theme-color" content="#7c4dff" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
|
||||
<base href="~/" />
|
||||
<link href="css/site.css" rel="stylesheet" />
|
||||
<link href="FrontOffice.Main.styles.css" rel="stylesheet" />
|
||||
<link rel="icon" type="image/png" href="favicon.png"/>
|
||||
<component type="typeof(HeadOutlet)" render-mode="Server" />
|
||||
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" rel="stylesheet" />
|
||||
<!-- Ensure latest MudBlazor CSS is used (cache-busting) -->
|
||||
<!-- MudBlazor CSS (باید قبل از site.css باشه تا override بشه) -->
|
||||
<link href="_content/MudBlazor/MudBlazor.min.css" rel="stylesheet" asp-append-version="true" />
|
||||
<!-- Custom styles (بعد از MudBlazor برای override) -->
|
||||
<link href="css/site.css" rel="stylesheet" asp-append-version="true" />
|
||||
<link href="FrontOffice.Main.styles.css" rel="stylesheet" />
|
||||
<!-- d3-org-chart custom styles -->
|
||||
<link href="css/org-chart.css" rel="stylesheet" asp-append-version="true" />
|
||||
</head>
|
||||
@@ -42,6 +46,7 @@
|
||||
<script src="js/d3-flextree.min.js"></script>
|
||||
<script src="js/d3-org-chart3.js"></script>
|
||||
<script src="js/org-chart.js" asp-append-version="true"></script>
|
||||
<script src="js/landing.js" asp-append-version="true"></script>
|
||||
|
||||
<script src="_framework/blazor.server.js"></script>
|
||||
<script>
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
@inject ImageCacheService ImageCache
|
||||
|
||||
@if (_loading)
|
||||
{
|
||||
<MudSkeleton SkeletonType="SkeletonType.Rectangle"
|
||||
Width="@(ImgWidth > 0 ? $"{ImgWidth}px" : SkeletonWidth)"
|
||||
Height="@(ImgHeight > 0 ? $"{ImgHeight}px" : SkeletonHeight)"
|
||||
Class="@Class" Style="@Style" />
|
||||
}
|
||||
else if (!string.IsNullOrWhiteSpace(_resolvedSrc))
|
||||
{
|
||||
<MudImage Src="@_resolvedSrc"
|
||||
Alt="@Alt"
|
||||
ObjectFit="@ObjectFit"
|
||||
ObjectPosition="@ObjectPosition"
|
||||
Elevation="@Elevation"
|
||||
Fluid="@Fluid"
|
||||
Width="@ImgWidth"
|
||||
Height="@ImgHeight"
|
||||
Style="@Style"
|
||||
Class="@Class" />
|
||||
}
|
||||
else if (!string.IsNullOrWhiteSpace(Fallback))
|
||||
{
|
||||
<div class="@($"d-flex align-center justify-center {Class}")" style="@Style">
|
||||
<MudIcon Icon="@Fallback" Size="Size.Large" Style="color:rgba(0,0,0,.15);" />
|
||||
</div>
|
||||
}
|
||||
|
||||
@code {
|
||||
/// <summary>مسیر تصویر (نسبی، data-URI، یا http)</summary>
|
||||
[Parameter] public string? Path { get; set; }
|
||||
|
||||
/// <summary>متن جایگزین</summary>
|
||||
[Parameter] public string? Alt { get; set; }
|
||||
|
||||
/// <summary>نحوه جایگیری تصویر</summary>
|
||||
[Parameter] public ObjectFit ObjectFit { get; set; } = ObjectFit.Cover;
|
||||
|
||||
/// <summary>موقعیت تصویر</summary>
|
||||
[Parameter] public ObjectPosition ObjectPosition { get; set; } = ObjectPosition.Center;
|
||||
|
||||
/// <summary>سایه تصویر</summary>
|
||||
[Parameter] public int Elevation { get; set; }
|
||||
|
||||
/// <summary>تصویر سیال (100% عرض)</summary>
|
||||
[Parameter] public bool Fluid { get; set; }
|
||||
|
||||
/// <summary>استایل inline</summary>
|
||||
[Parameter] public string? Style { get; set; }
|
||||
|
||||
/// <summary>کلاس CSS</summary>
|
||||
[Parameter] public string? Class { get; set; }
|
||||
|
||||
/// <summary>عرض تصویر (پیکسل) — مانند MudImage</summary>
|
||||
[Parameter] public int? ImgWidth { get; set; }
|
||||
|
||||
/// <summary>ارتفاع تصویر (پیکسل) — مانند MudImage</summary>
|
||||
[Parameter] public int? ImgHeight { get; set; }
|
||||
|
||||
/// <summary>عرض placeholder skeleton (مقدار CSS)</summary>
|
||||
[Parameter] public string? SkeletonWidth { get; set; }
|
||||
|
||||
/// <summary>ارتفاع placeholder skeleton (مقدار CSS)</summary>
|
||||
[Parameter] public string? SkeletonHeight { get; set; }
|
||||
|
||||
/// <summary>آیکون fallback هنگام عدم وجود تصویر</summary>
|
||||
[Parameter] public string? Fallback { get; set; }
|
||||
|
||||
private string? _resolvedSrc;
|
||||
private bool _loading;
|
||||
private string? _lastPath;
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
// فقط وقتی Path تغییر کرده، resolve مجدد انجام شود
|
||||
if (Path == _lastPath) return;
|
||||
_lastPath = Path;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(Path))
|
||||
{
|
||||
_resolvedSrc = null;
|
||||
_loading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// اگر قبلاً base64 هست → نمایش فوری
|
||||
if (Path.StartsWith("data:", StringComparison.Ordinal))
|
||||
{
|
||||
_resolvedSrc = Path;
|
||||
_loading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
_loading = true;
|
||||
StateHasChanged();
|
||||
|
||||
_resolvedSrc = await ImageCache.ResolveAsync(Path);
|
||||
_loading = false;
|
||||
}
|
||||
}
|
||||
@@ -1,168 +1,101 @@
|
||||
@if (InlineMode)
|
||||
{
|
||||
@* Inline rendering without MudDialog wrapper *@
|
||||
<MudStack Spacing="2">
|
||||
<MudText Typo="Typo.h5" Align="Align.Center">@GetDialogTitle()</MudText>
|
||||
@PhoneOrVerifyContent()
|
||||
</MudStack>
|
||||
<div class="auth-dialog-content">
|
||||
<MudStack Spacing="3" AlignItems="AlignItems.Center" Class="mb-4">
|
||||
<MudAvatar Size="Size.Large" Color="Color.Primary" Variant="Variant.Filled">
|
||||
<MudIcon Icon="@(_currentStep == AuthStep.Phone ? Icons.Material.Outlined.PhoneAndroid : Icons.Material.Outlined.LockOpen)" Size="Size.Large" />
|
||||
</MudAvatar>
|
||||
<MudText Typo="Typo.h5">@GetDialogTitle()</MudText>
|
||||
</MudStack>
|
||||
<PhoneVerifyForm @ref="_phoneVerifyForm"
|
||||
CurrentStep="_currentStep"
|
||||
PhoneRequest="_phoneRequest"
|
||||
VerifyRequest="_verifyRequest"
|
||||
CaptchaCode="@_captchaCode"
|
||||
CaptchaInput="@_captchaInput"
|
||||
CaptchaInputChanged="@(v => _captchaInput = v)"
|
||||
OnRefreshCaptcha="GenerateCaptcha"
|
||||
IsBusy="_isBusy"
|
||||
ErrorMessage="@_errorMessage"
|
||||
InfoMessage="@_infoMessage"
|
||||
PhoneNumber="@_phoneNumber"
|
||||
ResendRemaining="_resendRemaining"
|
||||
OnChangePhone="ChangePhoneAsync"
|
||||
OnResendOtp="ResendOtpAsync" />
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudDialog>
|
||||
<TitleContent>
|
||||
<MudText Typo="Typo.h4" Align="Align.Center">@GetDialogTitle()</MudText>
|
||||
</TitleContent>
|
||||
<MudDialog Class="auth-dialog-wrapper">
|
||||
<DialogContent>
|
||||
@PhoneOrVerifyContent()
|
||||
<div class="auth-dialog-content">
|
||||
<MudStack Spacing="2" AlignItems="AlignItems.Center" Class="mb-4 pt-2">
|
||||
<MudAvatar Size="Size.Large" Color="Color.Primary" Variant="Variant.Filled">
|
||||
<MudIcon Icon="@(_currentStep == AuthStep.Phone ? Icons.Material.Outlined.PhoneAndroid : Icons.Material.Outlined.LockOpen)" Size="Size.Large" />
|
||||
</MudAvatar>
|
||||
<MudText Typo="Typo.h5" Align="Align.Center">@GetDialogTitle()</MudText>
|
||||
</MudStack>
|
||||
|
||||
<PhoneVerifyForm @ref="_phoneVerifyForm"
|
||||
CurrentStep="_currentStep"
|
||||
PhoneRequest="_phoneRequest"
|
||||
VerifyRequest="_verifyRequest"
|
||||
CaptchaCode="@_captchaCode"
|
||||
CaptchaInput="@_captchaInput"
|
||||
CaptchaInputChanged="@(v => _captchaInput = v)"
|
||||
OnRefreshCaptcha="GenerateCaptcha"
|
||||
IsBusy="_isBusy"
|
||||
ErrorMessage="@_errorMessage"
|
||||
InfoMessage="@_infoMessage"
|
||||
PhoneNumber="@_phoneNumber"
|
||||
ResendRemaining="_resendRemaining"
|
||||
OnChangePhone="ChangePhoneAsync"
|
||||
OnResendOtp="ResendOtpAsync" />
|
||||
|
||||
<MudStack Class="mt-4" Spacing="2">
|
||||
@if (_currentStep == AuthStep.Phone)
|
||||
{
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
OnClick="SendOtpAsync"
|
||||
Disabled="_isBusy"
|
||||
Class="rounded-lg"
|
||||
FullWidth="true"
|
||||
Size="Size.Large">
|
||||
@if (_isBusy)
|
||||
{
|
||||
<MudProgressCircular Size="Size.Small" Indeterminate="true" Class="me-2" />
|
||||
}
|
||||
ارسال رمز پویا
|
||||
</MudButton>
|
||||
}
|
||||
else if (_currentStep == AuthStep.Verify)
|
||||
{
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
OnClick="VerifyOtpAsync"
|
||||
Disabled="_isBusy || IsVerificationLocked"
|
||||
Class="rounded-lg"
|
||||
FullWidth="true"
|
||||
Size="Size.Large">
|
||||
@if (_isBusy)
|
||||
{
|
||||
<MudProgressCircular Size="Size.Small" Indeterminate="true" Class="me-2" />
|
||||
}
|
||||
تأیید و ورود
|
||||
</MudButton>
|
||||
}
|
||||
@if (!HideCancelButton)
|
||||
{
|
||||
<MudButton Variant="Variant.Text"
|
||||
OnClick="Cancel"
|
||||
Disabled="_isBusy"
|
||||
FullWidth="true">لغو
|
||||
</MudButton>
|
||||
}
|
||||
</MudStack>
|
||||
</div>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
@if (!HideCancelButton)
|
||||
{
|
||||
<MudButton Variant="Variant.Text"
|
||||
OnClick="Cancel"
|
||||
Disabled="_isBusy">لغو
|
||||
</MudButton>
|
||||
}
|
||||
@if (_currentStep == AuthStep.Phone)
|
||||
{
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
OnClick="SendOtpAsync"
|
||||
Disabled="_isBusy"
|
||||
FullWidth="true">
|
||||
ارسال رمز پویا
|
||||
</MudButton>
|
||||
}
|
||||
else if (_currentStep == AuthStep.Verify)
|
||||
{
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
OnClick="VerifyOtpAsync"
|
||||
Disabled="_isBusy || IsVerificationLocked"
|
||||
FullWidth="true">
|
||||
تأیید و ورود
|
||||
</MudButton>
|
||||
}
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
}
|
||||
|
||||
@code {
|
||||
|
||||
private RenderFragment PhoneOrVerifyContent() => __builder =>
|
||||
{
|
||||
if (_currentStep == AuthStep.Phone)
|
||||
{
|
||||
// Phone Step
|
||||
__builder.OpenComponent(0, typeof(MudText));
|
||||
__builder.AddAttribute(1, "Typo", Typo.body2);
|
||||
__builder.AddAttribute(2, "Class", "mb-4");
|
||||
__builder.AddAttribute(3, "Align", Align.Center);
|
||||
__builder.AddContent(4, "لطفاً شماره موبایل خود را وارد کنید تا رمز پویا ارسال شود.");
|
||||
__builder.CloseComponent();
|
||||
|
||||
<MudForm @ref="_phoneForm" Model="_phoneRequest" Validation="@(_phoneRequestValidator.ValidateValue)">
|
||||
<MudTextField @bind-Value="_phoneRequest.Mobile"
|
||||
For="@(() => _phoneRequest.Mobile)"
|
||||
Label="شماره موبایل"
|
||||
InputType="InputType.Telephone"
|
||||
InputMode="InputMode.tel"
|
||||
Variant="Variant.Outlined"
|
||||
Immediate="true"
|
||||
Required="true"
|
||||
RequiredError="وارد کردن شماره موبایل الزامی است."
|
||||
HelperText="مثال: 09121234567"
|
||||
Class="mb-2"/>
|
||||
|
||||
@* @if (EnableCaptcha) *@
|
||||
@* { *@
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1" Class="mt-2 mb-2">
|
||||
<MudItem xs="7" md="12">
|
||||
<MudTextField xs="4" Label="کد کپچا" Placeholder="کد نمایش داده شده" Immediate="true"
|
||||
Variant="Variant.Outlined" @bind-Value="_captchaInput" Required="true"
|
||||
RequiredError="لطفاً کد کپچا را وارد کنید."/>
|
||||
</MudItem>
|
||||
<MudItem xs="4" >
|
||||
<MudPaper Elevation="1" Class="captcha-box d-flex align-center justify-center"
|
||||
Style="min-width:100px;min-height:48px;">
|
||||
<MudText Typo="Typo.h5">@_captchaCode</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
<MudItem xs="1" >
|
||||
<MudButton Variant="Variant.Text" Color="Color.Primary"
|
||||
StartIcon="@Icons.Material.Filled.Refresh" Disabled="_isBusy"
|
||||
OnClick="GenerateCaptcha">
|
||||
</MudButton>
|
||||
</MudItem>
|
||||
|
||||
</MudStack>
|
||||
|
||||
@* } *@
|
||||
@* *@
|
||||
<MudCheckBox T="bool" Required="true"
|
||||
Label="شرایط و قوانین را میپذیرم"
|
||||
RequiredError="برای ادامه باید شرایط و قوانین را بپذیرید."
|
||||
Class="mb-1"/>
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(_errorMessage))
|
||||
{
|
||||
<MudAlert Severity="Severity.Error" Dense="true" Elevation="0"
|
||||
Class="mb-2">@_errorMessage</MudAlert>
|
||||
}
|
||||
</MudForm>
|
||||
}
|
||||
else if (_currentStep == AuthStep.Verify)
|
||||
{
|
||||
// Verify Step
|
||||
<MudText Typo="Typo.body2" Class="mb-4" Align="Align.Center">رمز پویا شش رقمی ارسال شده به @_phoneNumber را
|
||||
وارد کنید.
|
||||
</MudText>
|
||||
|
||||
<MudForm @ref="_verifyForm" Model="_verifyRequest" Validation="@(_verifyRequestValidator.ValidateValue)">
|
||||
<MudTextField @bind-Value="_verifyRequest.Code"
|
||||
For="@(() => _verifyRequest.Code)"
|
||||
Label="رمز پویا"
|
||||
InputType="InputType.Telephone"
|
||||
InputMode="InputMode.tel"
|
||||
Variant="Variant.Outlined"
|
||||
Immediate="true"
|
||||
Required="true"
|
||||
RequiredError="وارد کردن رمز پویا الزامی است."
|
||||
HelperText="کد ۶ رقمی"
|
||||
Class="mb-2"
|
||||
MaxLength="6"/>
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(_errorMessage))
|
||||
{
|
||||
<MudAlert Severity="Severity.Error" Dense="true" Elevation="0"
|
||||
Class="mb-2">@(_errorMessage)</MudAlert>
|
||||
}
|
||||
@if (!string.IsNullOrWhiteSpace(_infoMessage))
|
||||
{
|
||||
<MudAlert Severity="Severity.Success" Dense="true" Elevation="0"
|
||||
Class="mb-2">@(_infoMessage)</MudAlert>
|
||||
}
|
||||
|
||||
<MudStack Spacing="2">
|
||||
<MudButton Variant="Variant.Text" Color="Color.Secondary" Disabled="_isBusy"
|
||||
OnClick="ChangePhoneAsync">تغییر شماره
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
<MudDivider Class="my-2"/>
|
||||
@if (_resendRemaining > 0)
|
||||
{
|
||||
<MudText Typo="Typo.body2" Align="Align.Center" Class="mud-text-secondary">امکان ارسال مجدد
|
||||
تا @_resendRemaining ثانیه دیگر
|
||||
</MudText>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudButton Variant="Variant.Text" Color="Color.Primary" Disabled="_isBusy" OnClick="ResendOtpAsync">
|
||||
ارسال مجدد رمز پویا
|
||||
</MudButton>
|
||||
}
|
||||
</MudForm>
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using Blazored.LocalStorage;
|
||||
using FrontOffice.BFF.User.Protobuf.Protos.User;
|
||||
using FrontOffice.BFF.User.Protobuf.Validator;
|
||||
using CMSMicroservice.Protobuf.Protos.User;
|
||||
using FrontOffice.Main.Utilities;
|
||||
using Grpc.Core;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
@@ -25,13 +24,10 @@ public partial class AuthDialog : IDisposable
|
||||
|
||||
public AuthStep _currentStep = AuthStep.Phone;
|
||||
|
||||
private readonly CreateNewOtpTokenRequestValidator _phoneRequestValidator = new();
|
||||
private readonly CreateNewOtpTokenRequest _phoneRequest = new();
|
||||
private MudForm? _phoneForm;
|
||||
private PhoneVerifyForm? _phoneVerifyForm;
|
||||
|
||||
private readonly VerifyOtpTokenRequestValidator _verifyRequestValidator = new();
|
||||
private readonly VerifyOtpTokenRequest _verifyRequest = new();
|
||||
private MudForm? _verifyForm;
|
||||
|
||||
private bool _isBusy;
|
||||
private string? _phoneNumber;
|
||||
@@ -82,11 +78,12 @@ public partial class AuthDialog : IDisposable
|
||||
public async Task SendOtpAsync()
|
||||
{
|
||||
_errorMessage = null;
|
||||
if (_phoneForm is null)
|
||||
var phoneForm = _phoneVerifyForm?.GetPhoneForm();
|
||||
if (phoneForm is null)
|
||||
return;
|
||||
|
||||
await _phoneForm.Validate();
|
||||
if (!_phoneForm.IsValid)
|
||||
await phoneForm.Validate();
|
||||
if (!phoneForm.IsValid)
|
||||
return;
|
||||
|
||||
// if (EnableCaptcha)
|
||||
@@ -105,10 +102,10 @@ public partial class AuthDialog : IDisposable
|
||||
|
||||
try
|
||||
{
|
||||
var validationResult = _phoneRequestValidator.Validate(_phoneRequest);
|
||||
if (!validationResult.IsValid)
|
||||
var validationResult = true; // _phoneRequestValidator.Validate(_phoneRequest);
|
||||
if (!validationResult)
|
||||
{
|
||||
_errorMessage = string.Join(" ", validationResult.Errors.Select(e => e.ErrorMessage).Distinct());
|
||||
_errorMessage = "لطفاً شماره تلفن معتبر وارد کنید."; // string.Join(" ", validationResult.Errors.Select(e => e.ErrorMessage).Distinct());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -157,11 +154,12 @@ public partial class AuthDialog : IDisposable
|
||||
_errorMessage = null;
|
||||
_infoMessage = null;
|
||||
|
||||
if (_verifyForm is null)
|
||||
var verifyForm = _phoneVerifyForm?.GetVerifyForm();
|
||||
if (verifyForm is null)
|
||||
return false;
|
||||
|
||||
await _verifyForm.Validate();
|
||||
if (!_verifyForm.IsValid)
|
||||
await verifyForm.Validate();
|
||||
if (!verifyForm.IsValid)
|
||||
return false;
|
||||
|
||||
if (IsVerificationLocked)
|
||||
@@ -189,10 +187,10 @@ public partial class AuthDialog : IDisposable
|
||||
_verifyRequest.ParentReferralCode = storedReferralCode;
|
||||
}
|
||||
|
||||
var validationResult = _verifyRequestValidator.Validate(_verifyRequest);
|
||||
if (!validationResult.IsValid)
|
||||
var validationResult = true; // _verifyRequestValidator.Validate(_verifyRequest);
|
||||
if (!validationResult)
|
||||
{
|
||||
_errorMessage = string.Join(" ", validationResult.Errors.Select(e => e.ErrorMessage).Distinct());
|
||||
_errorMessage = "لطفاً کد تأیید معتبر وارد کنید."; // string.Join(" ", validationResult.Errors.Select(e => e.ErrorMessage).Distinct());
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -239,6 +237,10 @@ public partial class AuthDialog : IDisposable
|
||||
return true;
|
||||
}
|
||||
|
||||
// اگه سرور RemainingAttempts برگردونده، از اون استفاده کن
|
||||
if (response.RemainingAttempts > 0)
|
||||
_attemptsLeft = response.RemainingAttempts;
|
||||
|
||||
RegisterFailedAttempt(string.IsNullOrWhiteSpace(response.Message) ? "کد نادرست است." : response.Message);
|
||||
}
|
||||
catch (RpcException rpcEx)
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
@using FrontOffice.BFF.ClubMembership.Protobuf.Protos.ClubMembership
|
||||
@using CMSMicroservice.Protobuf.Protos.ClubMembership
|
||||
@using CMSMicroservice.Protobuf.Protos.OtpToken
|
||||
@using Blazored.LocalStorage
|
||||
@using FrontOffice.Main.Utilities
|
||||
@using MudBlazor
|
||||
@inject ClubMembershipContract.ClubMembershipContractClient ClubMembershipClient
|
||||
@inject OtpTokenContract.OtpTokenContractClient OtpTokenClient
|
||||
@inject ILocalStorageService LocalStorage
|
||||
@inject AuthService AuthService
|
||||
@inject ISnackbar Snackbar
|
||||
@@ -129,23 +131,26 @@
|
||||
_isLoading = true;
|
||||
try
|
||||
{
|
||||
var response = await ClubMembershipClient.RequestClubContractOtpAsync(
|
||||
new RequestClubContractOtpRequest
|
||||
var response = await OtpTokenClient.CreateNewOtpTokenAsync(
|
||||
new CMSMicroservice.Protobuf.Protos.OtpToken.CreateNewOtpTokenRequest
|
||||
{
|
||||
Mobile = await GetUserMobileAsync(),
|
||||
Purpose = "signClubContract",
|
||||
SignGuid = _signGuid.ToString()
|
||||
});
|
||||
|
||||
if (response.Success)
|
||||
{
|
||||
_step = ContractStep.EnterOtp;
|
||||
_remainingSeconds = response.RemainingSeconds > 0 ? response.RemainingSeconds : 120;
|
||||
_remainingSeconds = 120; // Default 2 minutes
|
||||
StartCountdown();
|
||||
}
|
||||
else
|
||||
{
|
||||
Snackbar.Add(response.Message, Severity.Error);
|
||||
Snackbar.Add(response.Message ?? "ارسال رمز پویا با خطا مواجه شد.", Severity.Error);
|
||||
}
|
||||
}
|
||||
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"خطا در ارسال کد: {ex.Message}", Severity.Error);
|
||||
@@ -163,7 +168,7 @@
|
||||
try
|
||||
{
|
||||
var response = await ClubMembershipClient.AcceptClubMembershipContractAsync(
|
||||
new AcceptClubMembershipContractRequest
|
||||
new CMSMicroservice.Protobuf.Protos.ClubMembership.AcceptClubMembershipContractRequest
|
||||
{
|
||||
OtpCode = _otpCode,
|
||||
SignGuid = _signGuid.ToString(),
|
||||
@@ -172,21 +177,16 @@
|
||||
|
||||
if (response.Success)
|
||||
{
|
||||
// ذخیره توکن جدید
|
||||
if (!string.IsNullOrEmpty(response.Token))
|
||||
{
|
||||
await LocalStorage.SetItemAsync(TokenStorageKey, response.Token);
|
||||
await AuthService.InitUserAuthInfo();
|
||||
}
|
||||
|
||||
_step = ContractStep.Success;
|
||||
StopCountdown();
|
||||
Snackbar.Add("قرارداد باشگاه مشتریان با موفقیت امضا شد.", Severity.Success);
|
||||
}
|
||||
else
|
||||
{
|
||||
Snackbar.Add(response.Message, Severity.Error);
|
||||
Snackbar.Add(response.Message ?? "امضای قرارداد با خطا مواجه شد.", Severity.Error);
|
||||
}
|
||||
}
|
||||
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"خطا در ثبت قرارداد: {ex.Message}", Severity.Error);
|
||||
@@ -233,8 +233,7 @@
|
||||
|
||||
private string GetClubContractHtml()
|
||||
{
|
||||
return @"
|
||||
<div style='direction: rtl; font-family: Tahoma, sans-serif; line-height: 2; text-align: justify;'>
|
||||
return $@"<div style='direction: rtl; font-family: Tahoma, sans-serif; line-height: 2; text-align: justify;'>
|
||||
<h2 style='text-align: center; margin-bottom: 20px;'>قرارداد عضویت در باشگاه مشتریان کارابازار</h2>
|
||||
|
||||
<p><strong>ماده ۱ - تعاریف</strong></p>
|
||||
@@ -265,7 +264,21 @@
|
||||
<p>۶-۱. در صورت بروز اختلاف، طرفین ابتدا از طریق مذاکره اقدام به حل و فصل خواهند کرد.</p>
|
||||
<p>۶-۲. در صورت عدم توافق، مراجع قضایی صلاحیتدار رسیدگی خواهند کرد.</p>
|
||||
|
||||
<p style='text-align: center; margin-top: 30px;'><strong>شناسه قرارداد: " + _signGuid + @"</strong></p>
|
||||
<p style='text-align: center; margin-top: 30px;'><strong>شناسه قرارداد: {_signGuid}</strong></p>
|
||||
</div>";
|
||||
}
|
||||
|
||||
private async Task<string> GetUserMobileAsync()
|
||||
{
|
||||
// Get user mobile from auth service or localStorage
|
||||
var userInfo = await AuthService.GetUserAuthInfo();
|
||||
return userInfo?.MobileNumber ?? "";
|
||||
}
|
||||
|
||||
private async Task<long> GetCurrentUserIdAsync()
|
||||
{
|
||||
// Get current user ID from auth service
|
||||
var userInfo = await AuthService.GetUserAuthInfo();
|
||||
return userInfo?.UserId ?? 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
@inject IJSRuntime JS
|
||||
|
||||
@* ═══════════════════════════════════════════════
|
||||
EmptyState — Unified empty/not-found placeholder
|
||||
Usage: <EmptyState Title="سفارشی ثبت نشده" />
|
||||
<EmptyState Title="آدرسی یافت نشد"
|
||||
Icon="@Icons.Material.Filled.LocationOff"
|
||||
Description="ابتدا یک آدرس اضافه کنید."
|
||||
ActionText="افزودن آدرس"
|
||||
OnAction="OpenAddDialog" />
|
||||
═══════════════════════════════════════════════ *@
|
||||
|
||||
<MudStack AlignItems="AlignItems.Center" Class="py-12" Spacing="3">
|
||||
<MudIcon Icon="@Icon" Size="Size.Large" Color="Color.Default"
|
||||
Class="mud-text-disabled" Style="font-size:3rem;" />
|
||||
<MudText Typo="Typo.h6" Class="mud-text-secondary">@Title</MudText>
|
||||
@if (!string.IsNullOrWhiteSpace(Description))
|
||||
{
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary text-center"
|
||||
Style="max-width:400px;">@Description</MudText>
|
||||
}
|
||||
@if (ChildContent is not null)
|
||||
{
|
||||
@ChildContent
|
||||
}
|
||||
else if (!string.IsNullOrWhiteSpace(ActionText))
|
||||
{
|
||||
@if (!string.IsNullOrWhiteSpace(ActionHref))
|
||||
{
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary"
|
||||
OnClick="GoBack" Class="mt-2">@ActionText</MudButton>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary"
|
||||
OnClick="OnAction" Class="mt-2">@ActionText</MudButton>
|
||||
}
|
||||
}
|
||||
</MudStack>
|
||||
|
||||
@code {
|
||||
/// <summary>Material icon string.</summary>
|
||||
[Parameter] public string Icon { get; set; } = Icons.Material.Filled.Inbox;
|
||||
|
||||
/// <summary>Main heading text.</summary>
|
||||
[Parameter] public string Title { get; set; } = "موردی یافت نشد";
|
||||
|
||||
/// <summary>Optional secondary description.</summary>
|
||||
[Parameter] public string? Description { get; set; }
|
||||
|
||||
/// <summary>Optional CTA button text.</summary>
|
||||
[Parameter] public string? ActionText { get; set; }
|
||||
|
||||
/// <summary>If set, CTA becomes a link instead of callback.</summary>
|
||||
[Parameter] public string? ActionHref { get; set; }
|
||||
|
||||
/// <summary>CTA button callback.</summary>
|
||||
[Parameter] public EventCallback OnAction { get; set; }
|
||||
|
||||
/// <summary>Optional custom content (replaces ActionText button).</summary>
|
||||
[Parameter] public RenderFragment? ChildContent { get; set; }
|
||||
|
||||
private async Task GoBack()
|
||||
{
|
||||
await JS.InvokeVoidAsync("history.back");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
@* ═══════════════════════════════════════════════
|
||||
LoadingState — Unified loading indicator
|
||||
Usage: <LoadingState />
|
||||
<LoadingState Message="در حال دریافت سفارشات..." />
|
||||
═══════════════════════════════════════════════ *@
|
||||
|
||||
<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 {
|
||||
/// <summary>Optional loading message displayed below spinner.</summary>
|
||||
[Parameter] public string Message { get; set; } = "در حال بارگذاری...";
|
||||
}
|
||||
@@ -9,10 +9,10 @@
|
||||
<MudSnackbarProvider/>
|
||||
<MudLayout>
|
||||
<!-- AppBar -->
|
||||
<MudAppBar Elevation="0" Color="Color.Transparent" Dense="true" Class="py-2" id="top">
|
||||
<MudAppBar Elevation="1" Color="Color.Surface" Dense="true" Class="py-2" id="top">
|
||||
<MudContainer MaxWidth="MaxWidth.Large" Class="d-flex align-center justify-space-between w-100">
|
||||
|
||||
<MudHidden Breakpoint="Breakpoint.SmAndUp">
|
||||
<MudHidden Breakpoint="Breakpoint.MdAndUp">
|
||||
<MudIconButton Class="d-inline"
|
||||
Icon="@Icons.Material.Filled.Menu"
|
||||
OnClick="@ToggleDrawer"/>
|
||||
@@ -21,7 +21,7 @@
|
||||
<div class="d-flex align-center gap-2">
|
||||
<MudLink Href="@(RouteConstants.Main.MainPage)" Underline="Underline.None">
|
||||
<MudStack Row="true" Spacing="3" AlignItems="AlignItems.Center">
|
||||
<MudHidden Breakpoint="Breakpoint.SmAndUp" Invert="true">
|
||||
<MudHidden Breakpoint="Breakpoint.MdAndUp" Invert="true">
|
||||
<MudImage ObjectFit="ObjectFit.Cover"
|
||||
ObjectPosition="ObjectPosition.Center"
|
||||
Width="32"
|
||||
@@ -32,37 +32,66 @@
|
||||
<MudText Typo="Typo.h6">کارا بازار سلامت</MudText>
|
||||
</MudStack>
|
||||
</MudLink>
|
||||
|
||||
@* ── Store context badge (desktop) ── *@
|
||||
@if (IsInRegularStore)
|
||||
{
|
||||
<MudChip T="string" Color="Color.Success" Variant="Variant.Filled" Size="Size.Small"
|
||||
Class="d-none d-md-flex" Icon="@Icons.Material.Filled.Storefront">فروشگاه</MudChip>
|
||||
}
|
||||
else if (IsInDiscountStore)
|
||||
{
|
||||
<MudChip T="string" Color="Color.Error" Variant="Variant.Filled" Size="Size.Small"
|
||||
Class="d-none d-md-flex" Icon="@Icons.Material.Filled.Loyalty">فروشگاه تخفیفی</MudChip>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="d-none d-md-flex align-center gap-10">
|
||||
@if (_isAuthenticated)
|
||||
@if (IsInRegularStore && _isAuthenticated)
|
||||
{
|
||||
<MudLink Href="@(RouteConstants.Store.Products)" Typo="Typo.subtitle1" Class="mud-link">
|
||||
محصولات
|
||||
</MudLink>
|
||||
<MudLink Href="@(RouteConstants.Store.Cart)" Typo="Typo.subtitle1" Class="mud-link">سبد خرید
|
||||
</MudLink>
|
||||
<MudLink Href="@(RouteConstants.Store.Orders)" Typo="Typo.subtitle1" Class="mud-link">سفارشات
|
||||
من
|
||||
</MudLink>
|
||||
@* ── Regular store nav ── *@
|
||||
<MudLink Href="@(RouteConstants.Store.Products)" Typo="Typo.subtitle1" Class="mud-link">محصولات</MudLink>
|
||||
<MudLink Href="@(RouteConstants.Store.Cart)" Typo="Typo.subtitle1" Class="mud-link">سبد خرید</MudLink>
|
||||
<MudLink Href="@(RouteConstants.Store.Orders)" Typo="Typo.subtitle1" Class="mud-link">سفارشات من</MudLink>
|
||||
}
|
||||
<MudLink Href="@(RouteConstants.FAQ.Index)" Typo="Typo.subtitle1" Class="mud-link">سوالات متداول
|
||||
</MudLink>
|
||||
<MudLink Href="@(RouteConstants.Contact.Index)" Typo="Typo.subtitle1" Class="mud-link">ارتباط با
|
||||
ما
|
||||
</MudLink>
|
||||
<MudLink Href="@(RouteConstants.About.Index)" Typo="Typo.subtitle1" Class="mud-link">درباره ما
|
||||
</MudLink>
|
||||
else if (IsInDiscountStore && _isAuthenticated)
|
||||
{
|
||||
@* ── Discount store nav ── *@
|
||||
<MudLink Href="@(RouteConstants.DiscountStore.Products)" Typo="Typo.subtitle1" Class="mud-link">محصولات تخفیفی</MudLink>
|
||||
<MudLink Href="@(RouteConstants.DiscountStore.Cart)" Typo="Typo.subtitle1" Class="mud-link">سبد خرید</MudLink>
|
||||
<MudLink Href="@(RouteConstants.DiscountStore.Orders)" Typo="Typo.subtitle1" Class="mud-link">سفارشات من</MudLink>
|
||||
}
|
||||
else
|
||||
{
|
||||
@* ── Default global nav (not inside any store) ── *@
|
||||
@if (_isAuthenticated)
|
||||
{
|
||||
<MudLink Href="@(RouteConstants.Gateway.StoreChooser)" Typo="Typo.subtitle1" Class="mud-link">فروشگاهها</MudLink>
|
||||
}
|
||||
}
|
||||
<MudLink Href="@(RouteConstants.FAQ.Index)" Typo="Typo.subtitle1" Class="mud-link">سوالات متداول</MudLink>
|
||||
<MudLink Href="@(RouteConstants.Blog.Index)" Typo="Typo.subtitle1" Class="mud-link">بلاگ</MudLink>
|
||||
<MudLink Href="@(RouteConstants.Contact.Index)" Typo="Typo.subtitle1" Class="mud-link">ارتباط با ما</MudLink>
|
||||
<MudLink Href="@(RouteConstants.About.Index)" Typo="Typo.subtitle1" Class="mud-link">درباره ما</MudLink>
|
||||
</div>
|
||||
|
||||
<div class="d-flex align-center gap-2">
|
||||
@if (_isAuthenticated)
|
||||
{
|
||||
@* <MudBadge Content="@_cartCount" Color="Color.Error" Overlap="true" Visible="@(_cartCount > 0)"> *@
|
||||
@* *@
|
||||
@* </MudBadge> *@
|
||||
<MudIconButton Icon="@Icons.Material.Filled.ShoppingCart" Color="@(_cartCount > 0?Color.Success:Color.Inherit)"
|
||||
Href="@(RouteConstants.Store.Cart)" />
|
||||
@* ── Cart icon: only visible when inside a store ── *@
|
||||
@if (IsInRegularStore)
|
||||
{
|
||||
<MudIconButton Icon="@Icons.Material.Filled.ShoppingCart"
|
||||
Color="@(_cartCount > 0 ? Color.Success : Color.Inherit)"
|
||||
Href="@(RouteConstants.Store.Cart)" />
|
||||
}
|
||||
else if (IsInDiscountStore)
|
||||
{
|
||||
<MudIconButton Icon="@Icons.Material.Filled.ShoppingCart"
|
||||
Color="@(_discountCartCount > 0 ? Color.Error : Color.Inherit)"
|
||||
Href="@(RouteConstants.DiscountStore.Cart)" />
|
||||
}
|
||||
|
||||
@* دکمه بروزرسانی *@
|
||||
@if (_hasUpdate)
|
||||
{
|
||||
@@ -74,7 +103,10 @@
|
||||
</MudBadge>
|
||||
}
|
||||
<MudMenu Icon="@Icons.Material.Filled.Person" Color="Color.Inherit" Size="Size.Medium">
|
||||
<MudMenuItem OnClick="NavigateToProfile" Disabled="@(!AuthService.IsCompleteRegister())">
|
||||
<MudMenuItem OnClick="NavigateToProfile" Disabled="@(!_isCompleteRegister)" Icon="@Icons.Material.Outlined.Dashboard">
|
||||
داشبورد
|
||||
</MudMenuItem>
|
||||
<MudMenuItem OnClick="NavigateToHub" Disabled="@(!_isCompleteRegister)" Icon="@Icons.Material.Outlined.Person">
|
||||
پروفایل
|
||||
</MudMenuItem>
|
||||
<MudDivider/>
|
||||
@@ -110,19 +142,49 @@
|
||||
<MudStack Spacing="2">
|
||||
@if (_isAuthenticated)
|
||||
{
|
||||
<MudLink Href="@(RouteConstants.Store.Products)" Typo="Typo.subtitle1"
|
||||
OnClick="() => _drawerOpen=false">محصولات
|
||||
</MudLink>
|
||||
<MudLink Href="@(RouteConstants.Store.Cart)" Typo="Typo.subtitle1"
|
||||
OnClick="() => _drawerOpen=false">سبد خرید
|
||||
</MudLink>
|
||||
<MudLink Href="@(RouteConstants.Store.Orders)" Typo="Typo.subtitle1"
|
||||
OnClick="() => _drawerOpen=false">سفارشات من
|
||||
</MudLink>
|
||||
@if (IsInRegularStore)
|
||||
{
|
||||
<MudText Typo="Typo.overline" Class="mud-text-secondary">فروشگاه</MudText>
|
||||
<MudLink Href="@(RouteConstants.Store.Products)" Typo="Typo.subtitle1"
|
||||
OnClick="() => _drawerOpen=false">محصولات</MudLink>
|
||||
<MudLink Href="@(RouteConstants.Store.Cart)" Typo="Typo.subtitle1"
|
||||
OnClick="() => _drawerOpen=false">سبد خرید</MudLink>
|
||||
<MudLink Href="@(RouteConstants.Store.Orders)" Typo="Typo.subtitle1"
|
||||
OnClick="() => _drawerOpen=false">سفارشات من</MudLink>
|
||||
<MudDivider Class="my-1" />
|
||||
<MudLink Href="@(RouteConstants.Gateway.StoreChooser)" Typo="Typo.subtitle1"
|
||||
OnClick="() => _drawerOpen=false" Class="mud-text-secondary">
|
||||
← بازگشت به فروشگاهها
|
||||
</MudLink>
|
||||
}
|
||||
else if (IsInDiscountStore)
|
||||
{
|
||||
<MudText Typo="Typo.overline" Style="color:var(--mud-palette-error);">فروشگاه تخفیفی</MudText>
|
||||
<MudLink Href="@(RouteConstants.DiscountStore.Products)" Typo="Typo.subtitle1"
|
||||
OnClick="() => _drawerOpen=false">محصولات تخفیفی</MudLink>
|
||||
<MudLink Href="@(RouteConstants.DiscountStore.Cart)" Typo="Typo.subtitle1"
|
||||
OnClick="() => _drawerOpen=false">سبد خرید</MudLink>
|
||||
<MudLink Href="@(RouteConstants.DiscountStore.Orders)" Typo="Typo.subtitle1"
|
||||
OnClick="() => _drawerOpen=false">سفارشات من</MudLink>
|
||||
<MudDivider Class="my-1" />
|
||||
<MudLink Href="@(RouteConstants.Gateway.StoreChooser)" Typo="Typo.subtitle1"
|
||||
OnClick="() => _drawerOpen=false" Class="mud-text-secondary">
|
||||
← بازگشت به فروشگاهها
|
||||
</MudLink>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudLink Href="@(RouteConstants.Gateway.StoreChooser)" Typo="Typo.subtitle1"
|
||||
OnClick="() => _drawerOpen=false">فروشگاهها</MudLink>
|
||||
}
|
||||
<MudDivider Class="my-1" />
|
||||
}
|
||||
<MudLink Href="@(RouteConstants.FAQ.Index)" Typo="Typo.subtitle1" OnClick="() => _drawerOpen=false">
|
||||
سوالات متداول
|
||||
</MudLink>
|
||||
<MudLink Href="@(RouteConstants.Blog.Index)" Typo="Typo.subtitle1" OnClick="() => _drawerOpen=false">
|
||||
بلاگ
|
||||
</MudLink>
|
||||
<MudLink Href="@(RouteConstants.About.Index)" Typo="Typo.subtitle1" OnClick="() => _drawerOpen=false">
|
||||
درباره ما
|
||||
</MudLink>
|
||||
@@ -165,13 +227,93 @@
|
||||
</MudStack>
|
||||
</MudDrawer>
|
||||
|
||||
<MudMainContent>
|
||||
<MudMainContent Class="main-content-wrapper">
|
||||
@Body
|
||||
@if (DeviceDetector.IsDesktop())
|
||||
{
|
||||
<MudHidden Breakpoint="Breakpoint.SmAndDown">
|
||||
<Footer/>
|
||||
}
|
||||
</MudHidden>
|
||||
</MudMainContent>
|
||||
|
||||
<!-- Mobile Bottom Navigation -->
|
||||
<MudHidden Breakpoint="Breakpoint.MdAndUp">
|
||||
<MudPaper Class="bottom-nav" Elevation="8">
|
||||
<MudStack Row="true" Justify="Justify.SpaceAround" AlignItems="AlignItems.Center" Class="py-1">
|
||||
@if (IsInRegularStore && _isAuthenticated)
|
||||
{
|
||||
@* ── Regular store bottom nav ── *@
|
||||
<MudLink Href="@(RouteConstants.Store.Products)" Class="bottom-nav-item">
|
||||
<MudIcon Icon="@Icons.Material.Outlined.Storefront" Size="Size.Medium" />
|
||||
<MudText Typo="Typo.caption">محصولات</MudText>
|
||||
</MudLink>
|
||||
<MudLink Href="@(RouteConstants.Store.Cart)" Class="bottom-nav-item">
|
||||
<MudIcon Icon="@Icons.Material.Outlined.ShoppingCart" Size="Size.Medium" Color="@(_cartCount > 0 ? Color.Success : Color.Default)" />
|
||||
<MudText Typo="Typo.caption">سبد خرید</MudText>
|
||||
</MudLink>
|
||||
<MudLink Href="@(RouteConstants.Store.Orders)" Class="bottom-nav-item">
|
||||
<MudIcon Icon="@Icons.Material.Outlined.Receipt" Size="Size.Medium" />
|
||||
<MudText Typo="Typo.caption">سفارشات</MudText>
|
||||
</MudLink>
|
||||
<MudLink Href="@(RouteConstants.Profile.Index)" Class="bottom-nav-item">
|
||||
<MudIcon Icon="@Icons.Material.Outlined.Home" Size="Size.Medium" />
|
||||
<MudText Typo="Typo.caption">داشبورد</MudText>
|
||||
</MudLink>
|
||||
}
|
||||
else if (IsInDiscountStore && _isAuthenticated)
|
||||
{
|
||||
@* ── Discount store bottom nav ── *@
|
||||
<MudLink Href="@(RouteConstants.DiscountStore.Products)" Class="bottom-nav-item">
|
||||
<MudIcon Icon="@Icons.Material.Outlined.Loyalty" Size="Size.Medium" Color="Color.Error" />
|
||||
<MudText Typo="Typo.caption">محصولات</MudText>
|
||||
</MudLink>
|
||||
<MudLink Href="@(RouteConstants.DiscountStore.Cart)" Class="bottom-nav-item">
|
||||
<MudIcon Icon="@Icons.Material.Outlined.ShoppingCart" Size="Size.Medium" Color="@(_discountCartCount > 0 ? Color.Error : Color.Default)" />
|
||||
<MudText Typo="Typo.caption">سبد خرید</MudText>
|
||||
</MudLink>
|
||||
<MudLink Href="@(RouteConstants.DiscountStore.Orders)" Class="bottom-nav-item">
|
||||
<MudIcon Icon="@Icons.Material.Outlined.Receipt" Size="Size.Medium" />
|
||||
<MudText Typo="Typo.caption">سفارشات</MudText>
|
||||
</MudLink>
|
||||
<MudLink Href="@(RouteConstants.Profile.Index)" Class="bottom-nav-item">
|
||||
<MudIcon Icon="@Icons.Material.Outlined.Home" Size="Size.Medium" />
|
||||
<MudText Typo="Typo.caption">داشبورد</MudText>
|
||||
</MudLink>
|
||||
}
|
||||
else
|
||||
{
|
||||
@* ── Default bottom nav (outside stores) ── *@
|
||||
<MudLink Href="@(RouteConstants.Profile.Index)" Class="bottom-nav-item">
|
||||
<MudIcon Icon="@Icons.Material.Outlined.Home" Size="Size.Medium" />
|
||||
<MudText Typo="Typo.caption">خانه</MudText>
|
||||
</MudLink>
|
||||
@if (_isAuthenticated)
|
||||
{
|
||||
<MudLink Href="@(RouteConstants.Gateway.StoreChooser)" Class="bottom-nav-item">
|
||||
<MudIcon Icon="@Icons.Material.Outlined.Store" Size="Size.Medium" />
|
||||
<MudText Typo="Typo.caption">فروشگاهها</MudText>
|
||||
</MudLink>
|
||||
}
|
||||
<MudLink Href="@(RouteConstants.Blog.Index)" Class="bottom-nav-item">
|
||||
<MudIcon Icon="@Icons.Material.Outlined.Article" Size="Size.Medium" />
|
||||
<MudText Typo="Typo.caption">بلاگ</MudText>
|
||||
</MudLink>
|
||||
@if (_isAuthenticated)
|
||||
{
|
||||
<MudLink Href="@(RouteConstants.Profile.Hub)" Class="bottom-nav-item">
|
||||
<MudIcon Icon="@Icons.Material.Outlined.Person" Size="Size.Medium" />
|
||||
<MudText Typo="Typo.caption">پروفایل</MudText>
|
||||
</MudLink>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudLink Class="bottom-nav-item" @onclick="OpenAuthDialog">
|
||||
<MudIcon Icon="@Icons.Material.Outlined.Login" Size="Size.Medium" />
|
||||
<MudText Typo="Typo.caption">ورود</MudText>
|
||||
</MudLink>
|
||||
}
|
||||
}
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
</MudHidden>
|
||||
|
||||
</MudLayout>
|
||||
</MudRTLProvider>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Blazored.LocalStorage;
|
||||
using FrontOffice.Main.Utilities;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Routing;
|
||||
using Microsoft.JSInterop;
|
||||
using MudBlazor;
|
||||
using Microsoft.AspNetCore.Components.Authorization;
|
||||
@@ -16,6 +17,15 @@ public partial class MainLayout : IDisposable
|
||||
private bool _isAuthenticated;
|
||||
private string? _email;
|
||||
private int _cartCount;
|
||||
private int _discountCartCount;
|
||||
private bool _isCompleteRegister;
|
||||
|
||||
// ── Store Context ──
|
||||
private enum StoreContext { None, Regular, Discount }
|
||||
private StoreContext _storeCtx = StoreContext.None;
|
||||
private bool IsInRegularStore => _storeCtx == StoreContext.Regular;
|
||||
private bool IsInDiscountStore => _storeCtx == StoreContext.Discount;
|
||||
private bool IsInAnyStore => _storeCtx != StoreContext.None;
|
||||
|
||||
// متغیرهای بروزرسانی
|
||||
private bool _hasUpdate;
|
||||
@@ -26,6 +36,7 @@ public partial class MainLayout : IDisposable
|
||||
[Inject] private AuthService AuthService { get; set; } = default!;
|
||||
[Inject] private AuthDialogService AuthDialogService { get; set; } = default!;
|
||||
[Inject] private CartService CartService { get; set; } = default!;
|
||||
[Inject] private DiscountCartService DiscountCartService { get; set; } = default!;
|
||||
[Inject] private AppVersionService AppVersionService { get; set; } = default!;
|
||||
|
||||
private void ToggleTheme() => _isDark = !_isDark;
|
||||
@@ -35,20 +46,34 @@ public partial class MainLayout : IDisposable
|
||||
await JSRuntime.InvokeVoidAsync("history.back");
|
||||
}
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
Navigation.LocationChanged += OnLocationChanged;
|
||||
UpdateStoreContext();
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
await JSRuntime.InvokeVoidAsync("changeNavBgOnBodyScroll", "top", null, 1);
|
||||
await CheckAuthStatus();
|
||||
|
||||
await EnforceAuthGuardAsync();
|
||||
|
||||
if (_isAuthenticated)
|
||||
{
|
||||
// بررسی وضعیت ثبتنام کامل
|
||||
_isCompleteRegister = await AuthService.IsCompleteRegisterAsync();
|
||||
|
||||
// لود سبد خرید فقط برای کاربر لاگین شده
|
||||
await CartService.EnsureInitializedAsync();
|
||||
CartService.OnChange += OnCartChanged;
|
||||
_cartCount = CartService.Count;
|
||||
|
||||
await DiscountCartService.EnsureInitializedAsync();
|
||||
DiscountCartService.OnChange += OnDiscountCartChanged;
|
||||
_discountCartCount = DiscountCartService.Count;
|
||||
|
||||
// چک کردن بروزرسانی (بدون نمایش popup - فقط برای نشون دادن آیکون)
|
||||
await CheckForUpdateSilentlyAsync();
|
||||
}
|
||||
@@ -57,6 +82,83 @@ public partial class MainLayout : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
private void OnLocationChanged(object? sender, LocationChangedEventArgs e)
|
||||
{
|
||||
UpdateStoreContext();
|
||||
_ = InvokeAsync(async () =>
|
||||
{
|
||||
await EnforceAuthGuardAsync();
|
||||
StateHasChanged();
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If the current route requires authentication and the user is not logged in, redirect to home.
|
||||
/// </summary>
|
||||
private async Task EnforceAuthGuardAsync()
|
||||
{
|
||||
if (_isAuthenticated) return;
|
||||
|
||||
// Re-check in case token was set after initial render
|
||||
_isAuthenticated = await AuthService.IsAuthenticatedAsync();
|
||||
if (_isAuthenticated) return;
|
||||
|
||||
var path = new Uri(Navigation.Uri).AbsolutePath.ToLowerInvariant();
|
||||
if (IsProtectedRoute(path))
|
||||
{
|
||||
Navigation.NavigateTo(RouteConstants.Main.MainPage, replace: true);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsProtectedRoute(string path)
|
||||
{
|
||||
// Public routes — no auth needed
|
||||
if (path is "/" or "/register" or "/about" or "/faq" or "/contact"
|
||||
or "/packages" or "/stores" or "/products" or "/categories")
|
||||
return false;
|
||||
if (path.StartsWith("/blog")) return false;
|
||||
if (path.StartsWith("/package/")) return false;
|
||||
if (path.StartsWith("/product/")) return false;
|
||||
if (path == "/discount-store" || path.StartsWith("/discount-store/product/")) return false;
|
||||
|
||||
// Everything else is protected
|
||||
if (path.StartsWith("/profile")) return true;
|
||||
if (path.StartsWith("/commission")) return true;
|
||||
if (path.StartsWith("/network")) return true;
|
||||
if (path.StartsWith("/club")) return true;
|
||||
if (path.StartsWith("/checkout")) return true;
|
||||
if (path.StartsWith("/cart")) return true;
|
||||
if (path.StartsWith("/orders") || path.StartsWith("/order/") || path.StartsWith("/order-tracking/")) return true;
|
||||
if (path.StartsWith("/my-packages") || path.StartsWith("/my-orders") || path.StartsWith("/my-cart")) return true;
|
||||
if (path.StartsWith("/discount-store/cart") || path.StartsWith("/discount-store/checkout")
|
||||
|| path.StartsWith("/discount-store/orders") || path.StartsWith("/discount-store/order/")) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void UpdateStoreContext()
|
||||
{
|
||||
var path = new Uri(Navigation.Uri).AbsolutePath.ToLowerInvariant();
|
||||
|
||||
// Discount store routes all start with /discount-store
|
||||
if (path.StartsWith("/discount-store"))
|
||||
{
|
||||
_storeCtx = StoreContext.Discount;
|
||||
}
|
||||
// Regular store routes
|
||||
else if (path.StartsWith("/products") || path.StartsWith("/product/") ||
|
||||
path.StartsWith("/cart") || path.StartsWith("/checkout-summary") ||
|
||||
path.StartsWith("/orders") || path.StartsWith("/order/") ||
|
||||
path.StartsWith("/order-tracking/") || path.StartsWith("/categories"))
|
||||
{
|
||||
_storeCtx = StoreContext.Regular;
|
||||
}
|
||||
else
|
||||
{
|
||||
_storeCtx = StoreContext.None;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// چک کردن بروزرسانی بدون نمایش popup
|
||||
/// </summary>
|
||||
@@ -131,6 +233,12 @@ public partial class MainLayout : IDisposable
|
||||
InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
private void OnDiscountCartChanged()
|
||||
{
|
||||
_discountCartCount = DiscountCartService.Count;
|
||||
InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
private async Task CheckAuthStatus()
|
||||
{
|
||||
_isAuthenticated = await AuthService.IsAuthenticatedAsync();
|
||||
@@ -154,6 +262,11 @@ public partial class MainLayout : IDisposable
|
||||
Navigation.NavigateTo(RouteConstants.Profile.Index);
|
||||
}
|
||||
|
||||
private void NavigateToHub()
|
||||
{
|
||||
Navigation.NavigateTo(RouteConstants.Profile.Hub);
|
||||
}
|
||||
|
||||
private async Task Logout()
|
||||
{
|
||||
await AuthService.LogoutAsync();
|
||||
@@ -165,5 +278,7 @@ public partial class MainLayout : IDisposable
|
||||
public void Dispose()
|
||||
{
|
||||
CartService.OnChange -= OnCartChanged;
|
||||
DiscountCartService.OnChange -= OnDiscountCartChanged;
|
||||
Navigation.LocationChanged -= OnLocationChanged;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
@inject NavigationManager Navigation
|
||||
@inject IJSRuntime JS
|
||||
|
||||
<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 (ShowBack)
|
||||
{
|
||||
<MudButton Variant="Variant.Text" StartIcon="@Icons.Material.Filled.ArrowBack" OnClick="GoBack">بازگشت</MudButton>
|
||||
}
|
||||
</div>
|
||||
|
||||
@code {
|
||||
[Parameter] public string Title { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Optional subtitle text displayed below the title.
|
||||
/// </summary>
|
||||
[Parameter] public string? Subtitle { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Fallback URL if there's no browser history. Uses history.back() first.
|
||||
/// </summary>
|
||||
[Parameter] public string? BackHref { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether to show the back button. Default is true.
|
||||
/// </summary>
|
||||
[Parameter] public bool ShowBack { get; set; } = true;
|
||||
|
||||
private async Task GoBack()
|
||||
{
|
||||
await JS.InvokeVoidAsync("history.back");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
@using static FrontOffice.Main.Shared.AuthDialog
|
||||
|
||||
@if (CurrentStep == AuthStep.Phone)
|
||||
{
|
||||
<MudText Typo="Typo.body2" Class="mb-4 mud-text-secondary" Align="Align.Center">
|
||||
لطفاً شماره موبایل خود را وارد کنید تا رمز پویا ارسال شود.
|
||||
</MudText>
|
||||
|
||||
<MudForm @ref="_phoneForm" Model="PhoneRequest">
|
||||
<MudTextField @bind-Value="PhoneRequest.Mobile"
|
||||
Label="شماره موبایل"
|
||||
|
||||
Variant="Variant.Outlined"
|
||||
Immediate="true"
|
||||
Required="true"
|
||||
Placeholder="شماره موبایل خود را وارد کنید"
|
||||
RequiredError="وارد کردن شماره موبایل الزامی است."
|
||||
Class="mb-3" />
|
||||
|
||||
<div class="captcha-row mb-3">
|
||||
<MudTextField Label="کد کپچا" Placeholder="کد نمایش داده شده" Immediate="true"
|
||||
Variant="Variant.Outlined" Value="CaptchaInput"
|
||||
ValueChanged="@((string v) => CaptchaInputChanged.InvokeAsync(v))"
|
||||
Required="true"
|
||||
RequiredError="لطفاً کد کپچا را وارد کنید." />
|
||||
<MudPaper Elevation="0" Class="captcha-box d-flex align-center justify-center">
|
||||
<MudText Typo="Typo.h5">@CaptchaCode</MudText>
|
||||
</MudPaper>
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.Refresh" Color="Color.Primary"
|
||||
Disabled="IsBusy" OnClick="OnRefreshCaptcha" Size="Size.Medium" />
|
||||
</div>
|
||||
|
||||
<MudCheckBox T="bool" Required="true"
|
||||
Label="شرایط و قوانین را میپذیرم"
|
||||
RequiredError="برای ادامه باید شرایط و قوانین را بپذیرید."
|
||||
Color="Color.Primary"
|
||||
Class="mb-2" />
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(ErrorMessage))
|
||||
{
|
||||
<MudAlert Severity="Severity.Error" Dense="true" Elevation="0"
|
||||
Class="mb-2 rounded-lg">@ErrorMessage</MudAlert>
|
||||
}
|
||||
</MudForm>
|
||||
}
|
||||
else if (CurrentStep == AuthStep.Verify)
|
||||
{
|
||||
<MudText Typo="Typo.body2" Class="mb-4 mud-text-secondary" Align="Align.Center">
|
||||
رمز پویا شش رقمی ارسال شده به <strong>@PhoneNumber</strong> را وارد کنید.
|
||||
</MudText>
|
||||
|
||||
<MudForm @ref="_verifyForm" Model="VerifyRequest">
|
||||
<MudTextField @bind-Value="VerifyRequest.Code"
|
||||
Label="رمز پویا"
|
||||
|
||||
Variant="Variant.Outlined"
|
||||
Immediate="true"
|
||||
Required="true"
|
||||
RequiredError="وارد کردن رمز پویا الزامی است."
|
||||
|
||||
Class="mb-3"
|
||||
MaxLength="6" />
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(ErrorMessage))
|
||||
{
|
||||
<MudAlert Severity="Severity.Error" Dense="true" Elevation="0"
|
||||
Class="mb-2 rounded-lg">@ErrorMessage</MudAlert>
|
||||
}
|
||||
@if (!string.IsNullOrWhiteSpace(InfoMessage))
|
||||
{
|
||||
<MudAlert Severity="Severity.Success" Dense="true" Elevation="0"
|
||||
Class="mb-2 rounded-lg">@InfoMessage</MudAlert>
|
||||
}
|
||||
|
||||
<MudDivider Class="my-3" />
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
||||
<MudButton Variant="Variant.Text" Color="Color.Secondary" Disabled="IsBusy"
|
||||
OnClick="OnChangePhone" Size="Size.Small">
|
||||
<MudIcon Icon="@Icons.Material.Outlined.Edit" Size="Size.Small" Class="me-1" /> تغییر شماره
|
||||
</MudButton>
|
||||
@if (ResendRemaining > 0)
|
||||
{
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">
|
||||
ارسال مجدد تا @ResendRemaining ثانیه
|
||||
</MudText>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudButton Variant="Variant.Text" Color="Color.Primary" Disabled="IsBusy"
|
||||
OnClick="OnResendOtp" Size="Size.Small">
|
||||
ارسال مجدد
|
||||
</MudButton>
|
||||
}
|
||||
</MudStack>
|
||||
</MudForm>
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using CMSMicroservice.Protobuf.Protos.User;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
using static FrontOffice.Main.Shared.AuthDialog;
|
||||
|
||||
namespace FrontOffice.Main.Shared;
|
||||
|
||||
public partial class PhoneVerifyForm
|
||||
{
|
||||
// ── Step state ──
|
||||
[Parameter, EditorRequired] public AuthStep CurrentStep { get; set; }
|
||||
|
||||
// ── Phone step ──
|
||||
[Parameter, EditorRequired] public CreateNewOtpTokenRequest PhoneRequest { get; set; } = default!;
|
||||
|
||||
// ── Verify step ──
|
||||
[Parameter, EditorRequired] public VerifyOtpTokenRequest VerifyRequest { get; set; } = default!;
|
||||
|
||||
// ── Captcha ──
|
||||
[Parameter] public string? CaptchaCode { get; set; }
|
||||
[Parameter] public string? CaptchaInput { get; set; }
|
||||
[Parameter] public EventCallback<string?> CaptchaInputChanged { get; set; }
|
||||
[Parameter] public EventCallback OnRefreshCaptcha { get; set; }
|
||||
|
||||
// ── Shared state ──
|
||||
[Parameter] public bool IsBusy { get; set; }
|
||||
[Parameter] public string? ErrorMessage { get; set; }
|
||||
[Parameter] public string? InfoMessage { get; set; }
|
||||
[Parameter] public string? PhoneNumber { get; set; }
|
||||
[Parameter] public int ResendRemaining { get; set; }
|
||||
|
||||
// ── Verify actions ──
|
||||
[Parameter] public EventCallback OnChangePhone { get; set; }
|
||||
[Parameter] public EventCallback OnResendOtp { get; set; }
|
||||
|
||||
// ── Internal form refs (exposed via public methods) ──
|
||||
private MudForm? _phoneForm;
|
||||
private MudForm? _verifyForm;
|
||||
|
||||
public MudForm? GetPhoneForm() => _phoneForm;
|
||||
public MudForm? GetVerifyForm() => _verifyForm;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using FrontOffice.BFF.Configuration.Protobuf.Protos.AppVersion;
|
||||
using CMSMicroservice.Protobuf.Protos.AppVersion;
|
||||
using Blazored.LocalStorage;
|
||||
using Microsoft.JSInterop;
|
||||
|
||||
|
||||
@@ -6,7 +6,12 @@ public class AuthDialogService
|
||||
{
|
||||
private readonly IDialogService _dialogService;
|
||||
private readonly IDeviceDetector _deviceDetector;
|
||||
private readonly DialogOptions _normalWidth = new() { MaxWidth = MaxWidth.ExtraSmall, FullWidth = true };
|
||||
private readonly DialogOptions _dialogOptions = new()
|
||||
{
|
||||
MaxWidth = MaxWidth.ExtraSmall,
|
||||
FullWidth = true,
|
||||
CloseButton = true
|
||||
};
|
||||
|
||||
public AuthDialogService(IDialogService dialogService, IDeviceDetector deviceDetector)
|
||||
{
|
||||
@@ -18,12 +23,7 @@ public class AuthDialogService
|
||||
|
||||
public async Task ShowAuthDialogAsync()
|
||||
{
|
||||
// Pick dialog size based on device type
|
||||
var options = _deviceDetector.IsMobile()
|
||||
? new DialogOptions() { FullScreen = true}
|
||||
: _normalWidth;
|
||||
|
||||
var dialog = await _dialogService.ShowAsync<Shared.AuthDialog>("ورود به حساب کاربری", options);
|
||||
var dialog = await _dialogService.ShowAsync<Shared.AuthDialog>("ورود به حساب کاربری", _dialogOptions);
|
||||
var result = await dialog.Result;
|
||||
|
||||
if (!result.Canceled)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using Blazored.LocalStorage;
|
||||
using FrontOffice.BFF.User.Protobuf.Protos.User;
|
||||
using CMSMicroservice.Protobuf.Protos.User;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
@@ -12,7 +12,7 @@ public class AuthService
|
||||
private readonly NavigationManager _navigation;
|
||||
private readonly ISnackbar _snackbar;
|
||||
private readonly UserAuthInfo _userAuthInfo;
|
||||
private readonly UserContract.UserContractClient _userContract;
|
||||
private readonly CMSMicroservice.Protobuf.Protos.User.UserContract.UserContractClient _userContract;
|
||||
private readonly ILogger<AuthService> _logger;
|
||||
|
||||
private const string TokenStorageKey = "auth:token";
|
||||
@@ -22,7 +22,7 @@ public class AuthService
|
||||
NavigationManager navigation,
|
||||
ISnackbar snackbar,
|
||||
UserAuthInfo userAuthInfo,
|
||||
UserContract.UserContractClient userContract,
|
||||
CMSMicroservice.Protobuf.Protos.User.UserContract.UserContractClient userContract,
|
||||
ILogger<AuthService> logger)
|
||||
{
|
||||
_localStorage = localStorage;
|
||||
@@ -54,7 +54,7 @@ public class AuthService
|
||||
}
|
||||
public bool IsCompleteRegister()
|
||||
{
|
||||
InitUserAuthInfo().GetAwaiter();
|
||||
InitUserAuthInfo().GetAwaiter().GetResult();
|
||||
if (!string.IsNullOrWhiteSpace(_userAuthInfo.NationalCode) && !string.IsNullOrWhiteSpace(_userAuthInfo.FirstName) && !string.IsNullOrWhiteSpace(_userAuthInfo.LastName) && _userAuthInfo.IsSignMainContract)
|
||||
{
|
||||
return true;
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
namespace FrontOffice.Main.Utilities;
|
||||
|
||||
/// <summary>
|
||||
/// Service for fetching active blog categories (customer-facing).
|
||||
/// </summary>
|
||||
public class BlogCategoryService
|
||||
{
|
||||
private readonly CMSMicroservice.Protobuf.Protos.BlogCategory.BlogCategoryContract.BlogCategoryContractClient _client;
|
||||
|
||||
public BlogCategoryService(CMSMicroservice.Protobuf.Protos.BlogCategory.BlogCategoryContract.BlogCategoryContractClient client)
|
||||
{
|
||||
_client = client;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all active blog categories (for blog sidebar / filter).
|
||||
/// </summary>
|
||||
public async Task<List<BlogCategoryDto>> GetActiveCategoriesAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await _client.GetActiveBlogCategoriesAsync(
|
||||
new CMSMicroservice.Protobuf.Protos.BlogCategory.GetActiveBlogCategoriesRequest());
|
||||
|
||||
if (response?.Categories == null || response.Categories.Count == 0)
|
||||
return new();
|
||||
|
||||
return response.Categories
|
||||
.OrderBy(c => c.SortOrder)
|
||||
.Select(c => new BlogCategoryDto
|
||||
{
|
||||
Id = c.Id,
|
||||
Title = c.Title,
|
||||
Slug = c.Slug,
|
||||
Description = c.Description,
|
||||
IconName = c.IconName,
|
||||
PostCount = c.PostCount
|
||||
})
|
||||
.ToList();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
#if DEBUG
|
||||
Console.WriteLine($"BlogCategoryService.GetActiveCategoriesAsync error: {ex.Message}");
|
||||
#endif
|
||||
return new();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── DTO ──
|
||||
public class BlogCategoryDto
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public string Title { get; set; } = string.Empty;
|
||||
public string Slug { get; set; } = string.Empty;
|
||||
public string? Description { get; set; }
|
||||
public string? IconName { get; set; }
|
||||
public int PostCount { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
namespace FrontOffice.Main.Utilities;
|
||||
|
||||
/// <summary>
|
||||
/// Service for fetching published blog posts (customer-facing).
|
||||
/// Used by Landing Page (latest posts) and future Blog pages.
|
||||
/// </summary>
|
||||
public class BlogPostService
|
||||
{
|
||||
private readonly CMSMicroservice.Protobuf.Protos.BlogPost.BlogPostContract.BlogPostContractClient _client;
|
||||
|
||||
public BlogPostService(CMSMicroservice.Protobuf.Protos.BlogPost.BlogPostContract.BlogPostContractClient client)
|
||||
{
|
||||
_client = client;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get featured blog posts (for landing page hero / highlights).
|
||||
/// </summary>
|
||||
public async Task<List<BlogPostCardDto>> GetFeaturedPostsAsync(int count = 3)
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await _client.GetFeaturedBlogPostsAsync(
|
||||
new CMSMicroservice.Protobuf.Protos.BlogPost.GetFeaturedBlogPostsRequest { Count = count });
|
||||
|
||||
return MapToCardDtos(response?.Models);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
#if DEBUG
|
||||
Console.WriteLine($"BlogPostService.GetFeaturedPostsAsync error: {ex.Message}");
|
||||
#endif
|
||||
return new();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get latest published posts with optional category filter.
|
||||
/// </summary>
|
||||
public async Task<BlogPostListResult> GetPublishedPostsAsync(int page = 1, int pageSize = 9, string? search = null, long? categoryId = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var request = new CMSMicroservice.Protobuf.Protos.BlogPost.GetPublishedBlogPostsRequest
|
||||
{
|
||||
PaginationState = new CMSMicroservice.Protobuf.Protos.PaginationState
|
||||
{
|
||||
PageNumber = page,
|
||||
PageSize = pageSize
|
||||
}
|
||||
};
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(search))
|
||||
request.SearchTerm = search;
|
||||
if (categoryId.HasValue)
|
||||
request.CategoryId = categoryId.Value;
|
||||
|
||||
var response = await _client.GetPublishedBlogPostsAsync(request);
|
||||
|
||||
return new BlogPostListResult
|
||||
{
|
||||
Posts = MapToCardDtos(response?.Models),
|
||||
TotalCount = (int)(response?.MetaData?.TotalCount ?? 0),
|
||||
TotalPages = (int)(response?.MetaData?.TotalPage ?? 0),
|
||||
CurrentPage = page
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
#if DEBUG
|
||||
Console.WriteLine($"BlogPostService.GetPublishedPostsAsync error: {ex.Message}");
|
||||
#endif
|
||||
return new();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a single blog post by slug (for blog detail page).
|
||||
/// </summary>
|
||||
public async Task<BlogPostDetailDto?> GetBySlugAsync(string slug)
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await _client.GetBlogPostBySlugAsync(
|
||||
new CMSMicroservice.Protobuf.Protos.BlogPost.GetBlogPostBySlugRequest { Slug = slug });
|
||||
|
||||
if (response == null || response.Id <= 0) return null;
|
||||
|
||||
return new BlogPostDetailDto
|
||||
{
|
||||
Id = response.Id,
|
||||
Title = response.Title,
|
||||
Slug = response.Slug,
|
||||
Summary = response.Summary,
|
||||
HtmlContent = response.HtmlContent,
|
||||
FeaturedImagePath = response.FeaturedImagePath,
|
||||
FeaturedImageThumbnailPath = response.FeaturedImageThumbnailPath,
|
||||
PublishedAt = response.PublishedAt?.ToDateTime(),
|
||||
ViewCount = response.ViewCount,
|
||||
IsFeatured = response.IsFeatured,
|
||||
Categories = response.Categories?.Select(c => new BlogCategoryInfo { Id = c.Id, Title = c.Title, Slug = c.Slug }).ToList() ?? new(),
|
||||
Tags = response.Tags?.Select(t => new BlogTagInfo { Id = t.Id, Title = t.Title, Name = t.Name }).ToList() ?? new()
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
#if DEBUG
|
||||
Console.WriteLine($"BlogPostService.GetBySlugAsync error: {ex.Message}");
|
||||
#endif
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Increment view count for a blog post.
|
||||
/// </summary>
|
||||
public async Task IncrementViewCountAsync(long postId)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _client.IncrementViewCountAsync(
|
||||
new CMSMicroservice.Protobuf.Protos.BlogPost.IncrementViewCountRequest { Id = postId });
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Fire and forget — don't fail the page
|
||||
}
|
||||
}
|
||||
|
||||
// ── Helpers ──
|
||||
|
||||
private static List<BlogPostCardDto> MapToCardDtos(
|
||||
Google.Protobuf.Collections.RepeatedField<CMSMicroservice.Protobuf.Protos.BlogPost.BlogPostListItem>? models)
|
||||
{
|
||||
if (models == null || models.Count == 0) return new();
|
||||
|
||||
return models.Select(m => new BlogPostCardDto
|
||||
{
|
||||
Id = m.Id,
|
||||
Title = m.Title,
|
||||
Slug = m.Slug,
|
||||
Summary = m.Summary,
|
||||
ThumbnailUrl = m.FeaturedImageThumbnailPath,
|
||||
PublishedAt = m.PublishedAt?.ToDateTime(),
|
||||
ViewCount = m.ViewCount,
|
||||
IsFeatured = m.IsFeatured,
|
||||
Categories = m.Categories?.Select(c => new BlogCategoryInfo { Id = c.Id, Title = c.Title, Slug = c.Slug }).ToList() ?? new()
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// ── DTOs ──
|
||||
|
||||
public class BlogPostCardDto
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public string Title { get; set; } = string.Empty;
|
||||
public string Slug { get; set; } = string.Empty;
|
||||
public string? Summary { get; set; }
|
||||
public string? ThumbnailUrl { get; set; }
|
||||
public DateTime? PublishedAt { get; set; }
|
||||
public int ViewCount { get; set; }
|
||||
public bool IsFeatured { get; set; }
|
||||
public List<BlogCategoryInfo> Categories { get; set; } = new();
|
||||
}
|
||||
|
||||
public class BlogPostDetailDto
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public string Title { get; set; } = string.Empty;
|
||||
public string Slug { get; set; } = string.Empty;
|
||||
public string? Summary { get; set; }
|
||||
public string HtmlContent { get; set; } = string.Empty;
|
||||
public string? FeaturedImagePath { get; set; }
|
||||
public string? FeaturedImageThumbnailPath { get; set; }
|
||||
public DateTime? PublishedAt { get; set; }
|
||||
public int ViewCount { get; set; }
|
||||
public bool IsFeatured { get; set; }
|
||||
public List<BlogCategoryInfo> Categories { get; set; } = new();
|
||||
public List<BlogTagInfo> Tags { get; set; } = new();
|
||||
}
|
||||
|
||||
public class BlogPostListResult
|
||||
{
|
||||
public List<BlogPostCardDto> Posts { get; set; } = new();
|
||||
public int TotalCount { get; set; }
|
||||
public int TotalPages { get; set; }
|
||||
public int CurrentPage { get; set; }
|
||||
}
|
||||
|
||||
public class BlogCategoryInfo
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public string Title { get; set; } = string.Empty;
|
||||
public string Slug { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class BlogTagInfo
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public string Title { get; set; } = string.Empty;
|
||||
public string Name { get; set; } = string.Empty;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user