Compare commits
153 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 58037f0b49 | |||
| aae4b2693d | |||
| 61fd805c75 | |||
| ef6d233bbc | |||
| 3d84e726db | |||
| 13091849cf | |||
| 8e98c50bdf | |||
| 6123e47968 | |||
| acad1a719f | |||
| c7e6d01c11 | |||
| c764614d26 | |||
| 9203dc7924 | |||
| b64d99c5b3 | |||
| 4989cd00e0 | |||
| a708727241 | |||
| bed35935ad | |||
| 2339ca99b4 | |||
| ceaaac3e23 | |||
| 7f5a4f2c37 | |||
| db35814ce3 | |||
| 06100ca6dd | |||
| 7a5a2110c6 | |||
| 8dbc64cd86 | |||
| 2573729db5 | |||
| deadd197e6 | |||
| 5d58d354d7 | |||
| 671c8407e5 | |||
| 454d27f37e | |||
| 1956dcc5f4 | |||
| 37c7cebf92 | |||
| d59d829d5f | |||
| 81d139cdab | |||
| d44e99f4ab | |||
| cd835ae6f9 | |||
| 3c216feb32 | |||
| 7f8f99d4eb | |||
| 8766c29a5a | |||
| 8080b75f05 | |||
| 34c5c508e0 | |||
| 032b8eb448 | |||
| c2fd8790b6 | |||
| 088ea8dc7f | |||
| d8289237e9 | |||
| cd92a2f7b0 | |||
| 2874b931df | |||
| 0c9eb5f9ef | |||
| e5b1e11895 | |||
| 94fbf49fb9 | |||
| 0b1cc1c1c8 | |||
| eef13d1471 | |||
| 4330a5eaa5 | |||
| 77a835311a | |||
| 738119cf6d | |||
| 28a55246df | |||
| ceaf6de226 | |||
| f19711eabc | |||
| 310400b9e7 | |||
| 3600fc7709 | |||
| ae5ab1492e | |||
| 231da2cbaa | |||
| 1e5e6bcf3d | |||
| a1d67c5865 | |||
| d751c06a67 | |||
| b00af9e326 | |||
| 1c5aa0ce15 | |||
| 647e596050 | |||
| 7c86353a34 | |||
| cd0fb2fa57 | |||
| 4dc7b52289 | |||
| c793630962 | |||
| caaf28b08e | |||
| 1914f703db | |||
| ff3696cda3 | |||
| 2b1dc47dc7 | |||
| 2f9ef15859 | |||
| 5ded91a58a | |||
| 71b7e40994 | |||
| 3c1a8ffd53 | |||
| a3681a8bcd | |||
| ecc4f447ba | |||
| 474d364a25 | |||
| 816dcb70d1 | |||
| 3bffc13ffd | |||
| a956cb90a1 | |||
| 40882c8b80 | |||
| d71d463668 | |||
| 0bbc11e82c | |||
| 71f391ac80 | |||
| b82cac4af1 | |||
| a606cd93cd | |||
| ab8693b381 | |||
| 8b78235ef5 | |||
| 86f43d8fdd | |||
| f02d082d7b | |||
| a11a3765b8 | |||
| 2fa9dbc900 | |||
| 8e1f7d7a0c | |||
| 93b7375db5 | |||
| 00da4b98dd | |||
| 7371a263e8 | |||
| 214fe9efbd | |||
| 8cef475877 | |||
| 28695a88f8 | |||
| a2e1e1fead | |||
| 3cf5ad429d | |||
| 710ddbe261 | |||
| be2633ddaf | |||
| 104e4df1a6 | |||
| c08d776ecc | |||
| e2ca65ed98 | |||
| bed1ad7c66 | |||
| ae9b448a64 | |||
| 22203fd8f4 | |||
| bf0eb089bb | |||
| be78bc683a | |||
| 997ab80fa4 | |||
| bba9c1d1d8 | |||
| 183ce0a2e5 | |||
| 1ea928118d | |||
| 8f2d739d1f | |||
| f070a0b294 | |||
| 585109ab86 | |||
| 8e89d5e064 | |||
| c094c10da2 | |||
| 44ef166109 | |||
| aca6101e47 | |||
| 8bd961ade1 | |||
| 750d4c6b97 | |||
| 276a4c20f2 | |||
| 81f32e6ec1 | |||
| 0df5d6a595 | |||
| f60b007fdd | |||
| 01b9f1eb98 | |||
| 1dc60faf4a | |||
| fa9205bd8a | |||
| b102b89bfb | |||
| 6db83ccd90 | |||
| f0d1156457 | |||
| 83d0ca1e0c | |||
| 081b9d3c10 | |||
| eab978188a | |||
| 26ce7fd2b6 | |||
| 36838ac3ef | |||
| 88a86ecf69 | |||
| 95f8cb9f73 | |||
| eda53f9498 | |||
| 3e5fafaf2d | |||
| fb20fc59ef | |||
| 2273fa6a35 | |||
| 9ae9d53f4a | |||
| a6e0777974 | |||
| 9e92ab951a | |||
| 641f3651d7 |
@@ -0,0 +1,2 @@
|
|||||||
|
# Keep production config from current branch during merges — never overwrite
|
||||||
|
src/FrontOffice.Main/appsettings.Production.json merge=ours
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
|
|
||||||
name: Push nuget and docker image Actions Workflow
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- stage-new
|
|
||||||
jobs:
|
|
||||||
Deploy:
|
|
||||||
runs-on: windows
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: https://git.afrino.co/actions/checkout@v3
|
|
||||||
- name: Setup dotnet
|
|
||||||
uses: https://git.afrino.co/actions/setup-dotnet@v3
|
|
||||||
with:
|
|
||||||
dotnet-version: 7.0.x
|
|
||||||
|
|
||||||
- name: Remove Package Source
|
|
||||||
run: dotnet nuget remove source FourSat
|
|
||||||
continue-on-error: true
|
|
||||||
- name: Add Package Source
|
|
||||||
run: dotnet nuget add source --name FourSat --username systemuser --password sZSA7PTiv3pUSQZ https://git.afrino.co/api/packages/FourSat/nuget/index.json --store-password-in-clear-text
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: dotnet restore ".\src\FrontOffice.Main\FrontOffice.Main.csproj"
|
|
||||||
- name: Build
|
|
||||||
run: dotnet build ".\src\FrontOffice.Main\FrontOffice.Main.csproj" --configuration Release --no-restore
|
|
||||||
- name: Test
|
|
||||||
run: dotnet test ".\src\FrontOffice.Main\FrontOffice.Main.csproj" --no-restore --verbosity normal
|
|
||||||
- name: Recycle Apppool
|
|
||||||
run: |
|
|
||||||
& "C:\Windows\System32\inetsrv\appcmd.exe" recycle apppool /apppool.name:kbs1.ir
|
|
||||||
shell: powershell
|
|
||||||
- name: Stop Website
|
|
||||||
run: |
|
|
||||||
& "C:\Windows\System32\inetsrv\appcmd.exe" stop site /site.name:kbs1.ir
|
|
||||||
shell: powershell
|
|
||||||
- name: Publish
|
|
||||||
run: dotnet publish ".\src\FrontOffice.Main\FrontOffice.Main.csproj" -c Release -o publish
|
|
||||||
- name: Copy Publish To IIS Directory
|
|
||||||
run: Get-ChildItem -Path "publish\*" | Copy-Item -Destination "E:\kbs1.ir\kbs1.ir\" -Recurse -Force
|
|
||||||
- name: Start Website
|
|
||||||
run: |
|
|
||||||
& "C:\Windows\System32\inetsrv\appcmd.exe" start site /site.name:kbs1.ir
|
|
||||||
shell: powershell
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
name: Build and Deploy
|
name: Build and Deploy to Kubernetes
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@@ -6,63 +6,77 @@ on:
|
|||||||
- kub-stage
|
- kub-stage
|
||||||
|
|
||||||
env:
|
env:
|
||||||
REGISTRY: gitea-svc:3000
|
REGISTRY: 194.5.195.53:30080
|
||||||
IMAGE_NAME: admin/frontoffice
|
IMAGE_NAME: admin/frontoffice
|
||||||
|
K8S_SERVER: 194.5.195.53
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build-and-deploy:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container:
|
container:
|
||||||
image: docker:latest
|
image: 194.5.195.53:32082/docker-sshpass:latest
|
||||||
options: --privileged
|
options: --privileged
|
||||||
steps:
|
steps:
|
||||||
- name: Start Docker daemon with insecure registry
|
- name: Start Docker daemon
|
||||||
run: |
|
run: |
|
||||||
mkdir -p /etc/docker
|
mkdir -p /etc/docker
|
||||||
cat > /etc/docker/daemon.json << 'DAEMON'
|
cat > /etc/docker/daemon.json << 'DAEMON'
|
||||||
{
|
{
|
||||||
"insecure-registries": ["194.5.195.53:32500", "194.5.195.53:32082", "git.kbs1.ir", "gitea-svc:3000"],
|
"insecure-registries": ["194.5.195.53:30080", "194.5.195.53:32500", "194.5.195.53:32082"]
|
||||||
"dns": ["0.0.0.0"]
|
|
||||||
}
|
}
|
||||||
DAEMON
|
DAEMON
|
||||||
|
echo "🚀 Starting Docker daemon..."
|
||||||
|
dockerd --iptables=false --ip6tables=false --bridge=none --storage-driver=vfs &
|
||||||
|
|
||||||
# بدون اینترنت - فقط از cached images
|
# Wait up to 3 minutes for Docker to be ready
|
||||||
dockerd &
|
for i in $(seq 1 90); do
|
||||||
for i in $(seq 1 30); do
|
if docker info >/dev/null 2>&1; then
|
||||||
docker info >/dev/null 2>&1 && break || sleep 2
|
echo "✅ Docker daemon is ready (attempt $i)"
|
||||||
|
docker version
|
||||||
|
break
|
||||||
|
else
|
||||||
|
echo "⏳ Waiting for Docker daemon... (attempt $i/90)"
|
||||||
|
sleep 2
|
||||||
|
fi
|
||||||
done
|
done
|
||||||
docker info
|
|
||||||
|
|
||||||
# بررسی وجود cached images
|
# Final check
|
||||||
echo "📊 Checking cached base images..."
|
if ! docker info >/dev/null 2>&1; then
|
||||||
docker images | grep -E "mcr.microsoft.com|nginx" || echo "⚠️ WARNING: Base images not cached!"
|
echo "❌ Docker daemon failed to start after 3 minutes"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
run: |
|
run: |
|
||||||
git clone --depth 1 --branch kub-stage http://gitea-svc:3000/admin/FrontOffice.git .
|
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
|
- name: Build Docker Image
|
||||||
run: |
|
run: |
|
||||||
cd src
|
cd src
|
||||||
# استفاده از cached base images (بدون دانلود)
|
DOCKER_BUILDKIT=0 docker build --network host -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest -f FrontOffice.Main/Dockerfile .
|
||||||
DOCKER_BUILDKIT=0 docker build -f FrontOffice.Main/Dockerfile \
|
|
||||||
-t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} \
|
|
||||||
-t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest \
|
|
||||||
.
|
|
||||||
|
|
||||||
|
|
||||||
- name: Push to Registry
|
- name: Push to Registry
|
||||||
run: |
|
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
|
docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||||
|
|
||||||
- name: Build and Push Complete
|
- name: Deploy to Kubernetes
|
||||||
run: |
|
run: |
|
||||||
echo "🎉 Build and push completed successfully!"
|
export SSHPASS="${{ secrets.SERVER_PASSWORD }}"
|
||||||
echo "📦 Image pushed to: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}"
|
|
||||||
echo "📦 Latest tag: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest"
|
# Copy K8s manifests to server
|
||||||
echo ""
|
sshpass -e scp -o StrictHostKeyChecking=no k8s/staging/frontoffice-deployment.yaml root@${{ env.K8S_SERVER }}:/tmp/frontoffice-deployment.yaml
|
||||||
echo "🚀 To deploy manually on server:"
|
|
||||||
echo "kubectl set image deployment/frontoffice frontoffice=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}"
|
# Apply manifests and restart
|
||||||
|
sshpass -e ssh -o StrictHostKeyChecking=no root@${{ env.K8S_SERVER }} "
|
||||||
|
kubectl apply -f /tmp/frontoffice-deployment.yaml &&
|
||||||
|
crictl rmi ${REGISTRY}/${IMAGE_NAME}:latest 2>/dev/null || true &&
|
||||||
|
kubectl rollout restart deployment/frontoffice &&
|
||||||
|
kubectl rollout status deployment/frontoffice --timeout=180s &&
|
||||||
|
rm -f /tmp/frontoffice-deployment.yaml
|
||||||
|
"
|
||||||
|
echo "✅ Deployed!"
|
||||||
|
|||||||
@@ -22,11 +22,11 @@ jobs:
|
|||||||
mkdir -p /etc/docker
|
mkdir -p /etc/docker
|
||||||
cat > /etc/docker/daemon.json << 'DAEMON'
|
cat > /etc/docker/daemon.json << 'DAEMON'
|
||||||
{
|
{
|
||||||
"insecure-registries": ["194.5.195.53:30080", "194.5.195.53:32082"]
|
"insecure-registries": ["194.5.195.53:30080", "194.5.195.53:32500", "194.5.195.53:32082"]
|
||||||
}
|
}
|
||||||
DAEMON
|
DAEMON
|
||||||
echo "🚀 Starting Docker daemon..."
|
echo "🚀 Starting Docker daemon..."
|
||||||
dockerd &
|
dockerd --iptables=false --ip6tables=false --bridge=none --storage-driver=vfs &
|
||||||
|
|
||||||
for i in $(seq 1 90); do
|
for i in $(seq 1 90); do
|
||||||
if docker info >/dev/null 2>&1; then
|
if docker info >/dev/null 2>&1; then
|
||||||
@@ -48,21 +48,36 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
git clone --depth 1 --branch production http://gitea-svc:3000/admin/FrontOffice.git .
|
git clone --depth 1 --branch production http://gitea-svc:3000/admin/FrontOffice.git .
|
||||||
|
|
||||||
|
- 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
|
- name: Build Docker Image
|
||||||
run: |
|
run: |
|
||||||
cd src
|
cd src
|
||||||
docker build -f FrontOffice.Main/Dockerfile \
|
DOCKER_BUILDKIT=0 docker build --network host -f FrontOffice.Main/Dockerfile \
|
||||||
-t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} \
|
-t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} \
|
||||||
-t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:prod \
|
-t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:prod \
|
||||||
.
|
.
|
||||||
|
|
||||||
- name: Push to Registry
|
- name: Push to Registry
|
||||||
run: |
|
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 }}:${{ github.sha }}
|
||||||
docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:prod
|
docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:prod
|
||||||
|
|
||||||
- name: Deploy to Production
|
- name: Deploy to Production
|
||||||
run: |
|
run: |
|
||||||
sshpass -p "${{ secrets.K8S_SSH_PASSWORD }}" ssh -o StrictHostKeyChecking=no root@${{ env.K8S_SERVER }} \
|
export SSHPASS="${{ secrets.SERVER_PASSWORD }}"
|
||||||
"kubectl rollout restart deployment/frontoffice && kubectl rollout status deployment/frontoffice --timeout=5m" || echo "Deployment pending"
|
|
||||||
|
# Copy K8s manifests to server
|
||||||
|
sshpass -e scp -o StrictHostKeyChecking=no k8s/production/frontoffice-deployment.yaml root@${{ env.K8S_SERVER }}:/tmp/frontoffice-deployment.yaml
|
||||||
|
|
||||||
|
# Apply manifests and restart
|
||||||
|
sshpass -e ssh -o StrictHostKeyChecking=no root@${{ env.K8S_SERVER }} "
|
||||||
|
kubectl apply -f /tmp/frontoffice-deployment.yaml &&
|
||||||
|
kubectl rollout restart deployment/frontoffice &&
|
||||||
|
kubectl rollout status deployment/frontoffice --timeout=300s &&
|
||||||
|
rm -f /tmp/frontoffice-deployment.yaml
|
||||||
|
"
|
||||||
|
echo "✅ Deployed to Production!"
|
||||||
|
|||||||
+1
-1
@@ -14,7 +14,7 @@
|
|||||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||||
*.userprefs
|
*.userprefs
|
||||||
|
|
||||||
# Mono auto generated files
|
# Mono auto generated files
|
||||||
mono_crash.*
|
mono_crash.*
|
||||||
|
|
||||||
# Build results
|
# Build results
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# Docs moved to totalDoc
|
||||||
|
|
||||||
|
See [totalDoc/INDEX.md](../../totalDoc/INDEX.md) for all documentation.
|
||||||
|
|
||||||
@@ -0,0 +1,263 @@
|
|||||||
|
# سرویسهای FrontOffice (سمت مشتری)
|
||||||
|
|
||||||
|
این سند تمام سرویسهایی را که **FrontOffice** (فرانت مشتری) به **CMS** از طریق gRPC Gateway (`GW_URL`) فراخوانی میکند، به ترتیب **Use Case** فهرست میکند.
|
||||||
|
|
||||||
|
- **مسیر کد سرویسها:** `FrontOffice/src/FrontOffice.Main/Utilities/`
|
||||||
|
- **ثبت gRPC:** `FrontOffice/src/FrontOffice.Main/ConfigureServices.cs`
|
||||||
|
- **تاریخ:** ۱۴۰۵/۰۳/۱۷
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۱. احراز هویت و ثبتنام
|
||||||
|
|
||||||
|
| # | سرویس / متد | gRPC Contract | صفحه / کامپوننت | کاربرد |
|
||||||
|
|---|-------------|---------------|-----------------|--------|
|
||||||
|
| 1.1 | `AuthService` — `RefreshTokenAsync` | `UserContract.RefreshToken` | `/profile` (`Profile/Index`) | تمدید JWT پس از بازگشت از درگاه پرداخت |
|
||||||
|
| 1.2 | `AuthService` — `InitUserAuthInfo` / claims | — (local JWT parse) | سراسری | خواندن UserId، نام، کدملی، وضعیت قرارداد/پکیج/باشگاه از توکن |
|
||||||
|
| 1.3 | `UserContract.CreateNewOtpToken` | `UserContract` | `Shared/AuthDialog` | ارسال OTP ورود/ثبتنام با موبایل |
|
||||||
|
| 1.4 | `UserContract.VerifyOtpToken` | `UserContract` | `Shared/AuthDialog` | تأیید OTP و دریافت JWT |
|
||||||
|
| 1.5 | `UserContract.CreateNewOtpToken` (Purpose=`signContract`) | `UserContract` | `/register` (`RegisterWizard`) | ارسال OTP برای امضای قرارداد اصلی |
|
||||||
|
| 1.6 | `UserContract.AcceptContract` | `UserContract` | `/register` (`RegisterWizard`) | ثبت امضای قرارداد اصلی + دریافت توکن بهروز |
|
||||||
|
| 1.7 | `UserContract.GetUserForCustomer` | `UserContract` | `/register`, `/profile`, `/profile/personal`, `/profile/settings` | بارگذاری پروفایل موجود کاربر |
|
||||||
|
| 1.8 | `UserContract.UpdateCustomerProfile` | `UserContract` | `/register`, `/profile`, `/profile/personal` | ذخیره نام، نامخانوادگی، کدملی و… |
|
||||||
|
| 1.9 | `AuthDialogService.ShowAuthDialogAsync` | — (UI) | فروشگاه، سبد، checkout | نمایش دیالوگ ورود برای مهمان |
|
||||||
|
| 1.10 | `GuestActionGate` | — (wrapper روی Auth) | `/`, `/products`, `/product/{id}`, Discount Store | جلوگیری از افزودن به سبد بدون لاگین |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۲. پروفایل و آدرس
|
||||||
|
|
||||||
|
| # | سرویس / متد | gRPC Contract | صفحه / کامپوننت | کاربرد |
|
||||||
|
|---|-------------|---------------|-----------------|--------|
|
||||||
|
| 2.1 | `UserContract.GetUserForCustomer` | `UserContract` | `/profile`, `/profile/personal`, `/profile/settings` | نمایش و ویرایش اطلاعات شخصی |
|
||||||
|
| 2.2 | `UserContract.GetUser` | `UserContract` | `/profile/hub`, `Club/ActivationSection` | اطلاعات کامل کاربر (هاب پروفایل / پس از فعالسازی باشگاه) |
|
||||||
|
| 2.3 | `UserContract.UpdateUser` | `UserContract` | `/profile/settings` | بهروزرسانی تنظیمات حساب |
|
||||||
|
| 2.4 | `UserAddressContract.GetCustomerAddresses` | `UserAddressContract` | `/profile`, `/profile/addresses`, `/checkout`, Store/Discount checkout | لیست آدرسهای تحویل |
|
||||||
|
| 2.5 | `UserAddressContract.CreateCustomerAddress` | `UserAddressContract` | `Profile/Components/AddAddressDialog` | ثبت آدرس جدید |
|
||||||
|
| 2.6 | `UserAddressContract.UpdateCustomerAddress` | `UserAddressContract` | `Profile/Components/EditAddressDialog` | ویرایش آدرس |
|
||||||
|
| 2.7 | `UserAddressContract.DeleteCustomerAddress` / `DeleteUserAddress` | `UserAddressContract` | `/profile/addresses`, `/profile` | حذف آدرس |
|
||||||
|
| 2.8 | `UserAddressContract.SetCustomerDefaultAddress` / `SetAddressAsDefault` | `UserAddressContract` | `/profile/addresses`, `/profile`, `/checkout` | تنظیم آدرس پیشفرض |
|
||||||
|
| 2.9 | `CityContract.GetAllCitiesByFilter` | `CityContract` | `AddAddressDialog`, `EditAddressDialog` | جستجو و انتخاب شهر/استان |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۳. پکیج و خرید عضویت
|
||||||
|
|
||||||
|
| # | سرویس / متد | gRPC Contract | صفحه / کامپوننت | کاربرد |
|
||||||
|
|---|-------------|---------------|-----------------|--------|
|
||||||
|
| 3.1 | `PackageService.GetAllPackagesAsync` | `PackageContract.GetCustomerPackages` | `/packages`, `/my-packages`, `/`, `/checkout`, `PackagePurchaseDialog`, `ClubMembershipContractDialog`, Commission pages | لیست پکیجهای قابل خرید |
|
||||||
|
| 3.2 | `PackageService.GetPackageByIdAsync` | `PackageContract.GetCustomerPackageDetails` | (از طریق wrapper؛ صفحه جزئیات مستقیم RPC دارد) | جزئیات یک پکیج |
|
||||||
|
| 3.3 | `PackageContract.GetCustomerPackageDetails` | `PackageContract` | `/package/{id}` (`PackageDetail`) | جزئیات، ویژگیها و مشخصات پکیج |
|
||||||
|
| 3.4 | `PackageService.GetUserPackageStatusAsync` | `PackageContract.GetUserPackageStatus` | `/packages`, `/my-packages`, `/profile` | وضعیت خرید پکیج، باشگاه، موجودی کیف پول |
|
||||||
|
| 3.5 | `PackageContract.CustomerPurchasePackage` | `PackageContract` | `/profile` (`ProcessDirectPayment`), `/checkout` | شروع پرداخت درگاهی (Zarinpal) برای خرید پکیج |
|
||||||
|
| 3.6 | `PackageContract.CustomerVerifyPackagePurchase` | `PackageContract` | `/profile/payment-callback?type=package` | تأیید پرداخت پکیج پس از بازگشت از درگاه |
|
||||||
|
| 3.7 | `PackagePurchaseDialog` | — (UI + `PackageService`) | `/profile`, `/my-packages` | انتخاب پکیج و روش پرداخت (درگاه / Daya) |
|
||||||
|
| 3.8 | Daya Loan (external URL) | — | `/profile` | هدایت به `dayadiamond.ir` برای خرید اقساطی (بدون RPC) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۴. کیف پول، شارژ و برداشت
|
||||||
|
|
||||||
|
| # | سرویس / متد | gRPC Contract | صفحه / کامپوننت | کاربرد |
|
||||||
|
|---|-------------|---------------|-----------------|--------|
|
||||||
|
| 4.1 | `WalletService.GetBalancesAsync` | `UserWalletContract.GetCustomerWallet` | `/profile`, `/profile/wallet`, Store checkout, PaymentCallback | موجودی اصلی، تخفیفی و شبکه |
|
||||||
|
| 4.2 | `WalletService.GetTransactionsAsync` | `UserWalletContract.GetCustomerWalletHistory` | `/profile/wallet` | تاریخچه تراکنشهای کیف پول |
|
||||||
|
| 4.3 | `WalletService.GetMagicWalletStatusAsync` | `UserWalletContract.GetMagicWalletStatus` | `/profile`, `/profile/magic-wallet`, `/my-packages` | وضعیت کیف پول جادویی (Magic) |
|
||||||
|
| 4.4 | `WalletService.InitiateMagicChargeAsync` | `UserWalletContract.InitiateMagicCharge` | `/profile/magic-wallet` | شروع شارژ کیف پول جادویی → درگاه |
|
||||||
|
| 4.5 | `WalletService.VerifyMagicChargeAsync` | `UserWalletContract.VerifyMagicCharge` | `/profile/payment-callback?type=magic-wallet` | تأیید شارژ Magic پس از درگاه |
|
||||||
|
| 4.6 | `WalletService.InitiateDiscountChargeAsync` | `UserWalletContract.InitiateDiscountCharge` | `/profile/charge-discount-wallet` | شروع شارژ کیف پول تخفیفی → درگاه |
|
||||||
|
| 4.7 | `WalletService.VerifyDiscountChargeAsync` | `UserWalletContract.VerifyDiscountCharge` | `/profile/payment-callback?type=discount-wallet` | تأیید شارژ کیف تخفیفی |
|
||||||
|
| 4.8 | `WalletService.InitiateCreditChargeAsync` | `UserWalletContract.InitiateCreditCharge` | `/profile/charge-credit-wallet` | شروع شارژ کیف پول اصلی → درگاه |
|
||||||
|
| 4.9 | `WalletService.VerifyCreditChargeAsync` | `UserWalletContract.VerifyCreditCharge` | `/profile/payment-callback?type=credit-wallet` | تأیید شارژ کیف اصلی |
|
||||||
|
| 4.10 | `WalletService.GetWithdrawalsAsync` | `UserWalletContract.GetCustomerWithdrawals` | `/profile/withdrawal-requests` | لیست درخواستهای برداشت |
|
||||||
|
| 4.11 | `WalletService.GetWithdrawalSettingsAsync` | `UserWalletContract.GetCustomerWithdrawalSettings` | `/profile/withdrawal-requests` | حداقل مبلغ برداشت |
|
||||||
|
| 4.12 | `WalletService.RequestWithdrawalAsync` | `UserWalletContract.CustomerWithdrawBalance` | `/profile/withdrawal-requests` | ثبت درخواست برداشت پاداش (با payoutId کمیسیون) |
|
||||||
|
| 4.13 | `CommissionService.GetWithdrawablePayoutsAsync` | `CommissionContract.GetMyCommissionPayouts` | `/profile/withdrawal-requests` | پاداشهای قابل برداشت برای انتخاب |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۵. Callback پرداخت (مشترک)
|
||||||
|
|
||||||
|
| # | سرویس / متد | gRPC Contract | صفحه / کامپوننت | کاربرد |
|
||||||
|
|---|-------------|---------------|-----------------|--------|
|
||||||
|
| 5.1 | `PackageContract.CustomerVerifyPackagePurchase` | `PackageContract` | `/profile/payment-callback` (type=package) | تأیید خرید پکیج |
|
||||||
|
| 5.2 | `WalletService.VerifyMagicChargeAsync` | `UserWalletContract` | `/profile/payment-callback` (type=magic-wallet) | تأیید شارژ Magic |
|
||||||
|
| 5.3 | `WalletService.VerifyDiscountChargeAsync` | `UserWalletContract` | `/profile/payment-callback` (type=discount-wallet) | تأیید شارژ تخفیفی |
|
||||||
|
| 5.4 | `WalletService.VerifyCreditChargeAsync` | `UserWalletContract` | `/profile/payment-callback` (type=credit-wallet) | تأیید شارژ کیف اصلی |
|
||||||
|
| 5.5 | `DiscountOrderService.VerifyDiscountOrderPaymentAsync` | `DiscountOrderContract.CustomerVerifyDiscountOrderPayment` | `/profile/payment-callback` (type=discount-order) | تأیید پرداخت سفارش فروشگاه تخفیفی |
|
||||||
|
| 5.6 | `UserContract.GetUserForCustomer` + `AuthService.InitUserAuthInfo` | `UserContract` | `/profile/payment-callback` | بهروزرسانی claims کاربر پس از پرداخت موفق |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۶. فروشگاه اصلی (Store)
|
||||||
|
|
||||||
|
| # | سرویس / متد | gRPC Contract | صفحه / کامپوننت | کاربرد |
|
||||||
|
|---|-------------|---------------|-----------------|--------|
|
||||||
|
| 6.1 | `ProductService.GetProductsPagedAsync` | `ProductsContract.GetProductsForCustomer` | `/products` | لیست محصولات با فیلتر/صفحهبندی |
|
||||||
|
| 6.2 | `ProductService.GetTopSellingAsync` | `ProductsContract` | `/` (صفحه اصلی) | پرفروشترین محصولات |
|
||||||
|
| 6.3 | `ProductService.GetByIdAsync` | `ProductsContract.GetProductByIdForCustomer` | `/product/{id}` | جزئیات محصول + گالری |
|
||||||
|
| 6.4 | `CategoryService.GetAllAsync` / `GetByIdAsync` | `CategoryContract.GetAllCategoriesForCustomer` | `/categories`, `/products` | درخت دستهبندی محصولات |
|
||||||
|
| 6.5 | `CartService` — Add/Update/Remove/Get | `UserCartsContract.*ForCustomer` | `/`, `/products`, `/product/{id}`, `/cart` | مدیریت سبد خرید (AddNew, Update, Remove, GetCustomerCart) |
|
||||||
|
| 6.6 | `UserOrderContract.SubmitShopBuyOrder` | `UserOrderContract` | `/checkout-summary` | ثبت سفارش و پرداخت از کیف پول |
|
||||||
|
| 6.7 | `OrderService.GetOrdersAsync` | `UserOrderContract.GetCustomerOrders` | `/orders` | لیست سفارشهای کاربر |
|
||||||
|
| 6.8 | `OrderService.GetOrderAsync` | `UserOrderContract.GetCustomerOrder` | `/order/{id}`, `/order-tracking/{id}` | جزئیات یک سفارش |
|
||||||
|
| 6.9 | `OrderService.CalculateOrderPVAsync` | `UserOrderContract.CalculateOrderPV` | `/order/{id}` | محاسبه PV سفارش |
|
||||||
|
| 6.10 | `VATService.GetRateAsync` | `UserOrderContract` (via scope) | Store/Discount صفحات قیمت | نرخ مالیات بر ارزش افزوده (کش روزانه) |
|
||||||
|
| 6.11 | `WalletService.GetBalancesAsync` | `UserWalletContract` | `/checkout-summary` | بررسی موجودی برای پرداخت سفارش |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۷. فروشگاه تخفیفی (Discount Store)
|
||||||
|
|
||||||
|
| # | سرویس / متد | gRPC Contract | صفحه / کامپوننت | کاربرد |
|
||||||
|
|---|-------------|---------------|-----------------|--------|
|
||||||
|
| 7.1 | `DiscountProductService.GetProductsAsync` | `DiscountProductContract.GetDiscountProducts` | `/discount-store` | لیست محصولات تخفیفی |
|
||||||
|
| 7.2 | `DiscountProductService.GetTopSellingAsync` | `DiscountProductContract` | `/` | پرفروشهای فروشگاه تخفیفی |
|
||||||
|
| 7.3 | `DiscountProductService.GetByIdAsync` | `DiscountProductContract.GetDiscountProductById` + Images | `/discount-store/product/{id}` | جزئیات محصول تخفیفی |
|
||||||
|
| 7.4 | `DiscountProductService.GetCategoriesAsync` | `DiscountCategoryContract.GetDiscountCategories` | `/discount-store` | دستهبندیهای فروشگاه تخفیفی |
|
||||||
|
| 7.5 | `DiscountCartService` — Add/Update/Remove/Get | `DiscountShoppingCartContract.*` | `/discount-store`, `/discount-store/cart` | سبد خرید تخفیفی |
|
||||||
|
| 7.6 | `DiscountOrderService.PlaceOrderAsync` | `DiscountOrderContract.PlaceOrder` | `/discount-store/checkout` | ثبت سفارش (کیف تخفیفی + درگاه) |
|
||||||
|
| 7.7 | `DiscountOrderService.GetUserOrdersAsync` | `DiscountOrderContract.GetUserOrders` | `/discount-store/orders` | لیست سفارشهای تخفیفی |
|
||||||
|
| 7.8 | `DiscountOrderService.GetOrderByIdAsync` | `DiscountOrderContract.GetOrderById` | `/discount-store/order/{id}` | جزئیات سفارش تخفیفی |
|
||||||
|
| 7.9 | `UserAddressContract.GetCustomerAddresses` | `UserAddressContract` | `/discount-store/checkout` | انتخاب آدرس تحویل |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۸. باشگاه مشتریان (Club)
|
||||||
|
|
||||||
|
| # | سرویس / متد | gRPC Contract | صفحه / کامپوننت | کاربرد |
|
||||||
|
|---|-------------|---------------|-----------------|--------|
|
||||||
|
| 8.1 | `ClubMembershipService.GetMyMembershipAsync` | `ClubMembershipContract.GetClubMembership` | `/club/membership` | وضعیت عضویت باشگاه |
|
||||||
|
| 8.2 | `ClubConfigurationService.GetClubConfigurationAsync` | `ConfigurationContract.GetClubConfiguration` | `/club/membership` | هزینه فعالسازی و هدیه عضویت |
|
||||||
|
| 8.3 | `ClubConfigurationService.GetClubFeaturesAsync` | `ConfigurationContract.GetClubFeatures` | `/club/features` | لیست امکانات باشگاه |
|
||||||
|
| 8.4 | `ClubMembershipService.ActivateMembershipAsync` | `ClubMembershipContract.ActivateClubMembership` | `Club/ActivationSection` | فعالسازی/تمدید عضویت باشگاه |
|
||||||
|
| 8.5 | `OtpTokenContract.CreateNewOtpToken` | `OtpTokenContract` | `ClubMembershipContractDialog` | OTP برای امضای قرارداد باشگاه |
|
||||||
|
| 8.6 | `ClubMembershipContract.AcceptClubMembershipContract` | `ClubMembershipContract` | `ClubMembershipContractDialog` | ثبت امضای قرارداد باشگاه |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۹. شبکه فروش (Network / MLM)
|
||||||
|
|
||||||
|
| # | سرویس / متد | gRPC Contract | صفحه / کامپوننت | کاربرد |
|
||||||
|
|---|-------------|---------------|-----------------|--------|
|
||||||
|
| 9.1 | `NetworkMembershipService.GetMyNetworkTreeAsync` | `NetworkMembershipContract.GetMyNetworkTree` | `/profile/tree` (`OrganizationChart`) | درخت سازمان فروش کاربر |
|
||||||
|
| 9.2 | `NetworkMembershipService.GetSubordinateTreeAsync` | `NetworkMembershipContract.GetSubordinateTree` | `/profile/tree` | drill-down به زیرمجموعه |
|
||||||
|
| 9.3 | `NetworkMembershipService.GetMyNetworkStatisticsAsync` | `NetworkMembershipContract.GetMyNetworkStatistics` | `/profile/tree`, `/network/statistics` | آمار پا چپ/راست، تعداد اعضا |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۱۰. کمیسیون و پاداش
|
||||||
|
|
||||||
|
| # | سرویس / متد | gRPC Contract | صفحه / کامپوننت | کاربرد |
|
||||||
|
|---|-------------|---------------|-----------------|--------|
|
||||||
|
| 10.1 | `CommissionService.GetWeekDefinitionsAsync` | `CommissionContract.GetWeekDefinitions` | `Shared/WeekSelector`, Commission pages | لیست هفتههای محاسبه پاداش |
|
||||||
|
| 10.2 | `CommissionService.GetMyCommissionPayoutsAsync` | `CommissionContract.GetMyCommissionPayouts` | `/commission/dashboard` | تاریخچه پرداخت پاداشها |
|
||||||
|
| 10.3 | `CommissionService.GetMyWeeklyBalanceAsync` | `CommissionContract.GetMyWeeklyBalances` | `/commission/weekly-balance` | جزئیات بالانس هفتگی (چپ/راست) |
|
||||||
|
| 10.4 | `PackageService.GetAllPackagesAsync` | `PackageContract` | Commission pages | فیلتر پاداش بر اساس پکیج |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۱۱. محتوای سایت، بلاگ و صفحات ثابت
|
||||||
|
|
||||||
|
| # | سرویس / متد | gRPC Contract | صفحه / کامپوننت | کاربرد |
|
||||||
|
|---|-------------|---------------|-----------------|--------|
|
||||||
|
| 11.1 | `SitePageSettingsService.GetPageAsync` | `SitePageSettingsContract.GetPageSettings` | `/` (landing), `/about`, `/contact`, `/licenses` | محتوای داینامیک صفحات (hero، تصاویر، JSON تنظیمات) |
|
||||||
|
| 11.2 | `BlogPostService.GetFeaturedPostsAsync` | `BlogPostContract.GetFeaturedBlogPosts` | `/`, `/profile` | پستهای ویژه |
|
||||||
|
| 11.3 | `BlogPostService.GetPublishedPostsAsync` | `BlogPostContract.GetPublishedBlogPosts` | `/`, `/blog` | لیست پستهای منتشرشده |
|
||||||
|
| 11.4 | `BlogPostService.GetBySlugAsync` | `BlogPostContract.GetBlogPostBySlug` | `/blog/{slug}` | محتوای کامل یک پست |
|
||||||
|
| 11.5 | `BlogPostService.IncrementViewCountAsync` | `BlogPostContract.IncrementViewCount` | `/blog/{slug}` | افزایش شمارنده بازدید |
|
||||||
|
| 11.6 | `BlogCategoryService.GetActiveCategoriesAsync` | `BlogCategoryContract` | `/blog` | فیلتر دستهبندی بلاگ |
|
||||||
|
| 11.7 | FAQ | — (hardcoded) | `/faq` | سوالات متداول — **بدون فراخوانی API** |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۱۲. زیرساخت مشترک (Infrastructure)
|
||||||
|
|
||||||
|
| # | سرویس / متد | gRPC Contract | صفحه / کامپوننت | کاربرد |
|
||||||
|
|---|-------------|---------------|-----------------|--------|
|
||||||
|
| 12.1 | `ImageCacheService.ResolveAsync` | `ImageResolverContract` | `Shared/AppImage` (سراسری) | resolve مسیر تصویر CMS به data-URI |
|
||||||
|
| 12.2 | `AppVersionService.CheckVersionAsync` | `AppVersionContract` | `App.razor`, `MainLayout` | بررسی نسخه جدید اپ |
|
||||||
|
| 12.3 | `AppVersionService.ApplyUpdateAsync` / `SkipVersionAsync` | — (localStorage + reload) | `App.razor`, `MainLayout` | اعمال یا رد آپدیت |
|
||||||
|
| 12.4 | `MainService.OnChangeHandler` | — (in-memory) | `/`, `AuthDialog` | اطلاعرسانی تغییر state سراسری (مثلاً پس از login) |
|
||||||
|
| 12.5 | `IChromiumPdfService` | — (local endpoint `/contract/generate`) | `/register` | تولید PDF قرارداد (Chromium headless) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۱۳. Gateway / انتخابگر
|
||||||
|
|
||||||
|
| صفحه | سرویس | کاربرد |
|
||||||
|
|------|--------|--------|
|
||||||
|
| `/stores` | — | انتخاب بین فروشگاه اصلی و تخفیفی |
|
||||||
|
| `/my-orders` | — | انتخاب لیست سفارشها |
|
||||||
|
| `/my-cart` | — | انتخاب سبد خرید |
|
||||||
|
|
||||||
|
این صفحات فقط مسیریابی UI هستند و مستقیماً gRPC صدا نمیزنند.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۱۴. سرویسهای ثبتشده ولی بدون استفاده در UI
|
||||||
|
|
||||||
|
| سرویس / Contract | وضعیت |
|
||||||
|
|------------------|--------|
|
||||||
|
| `SitePageService` (`SitePageContract`) | ثبت در DI؛ **هیچ صفحهای inject نمیکند** (جایگزین: `SitePageSettingsService`) |
|
||||||
|
| `TransactionsContract` | ثبت در DI؛ **استفاده نشده** |
|
||||||
|
| `UserWalletHistoryContract` | ثبت در DI؛ **استفاده نشده** (تاریخچه از `UserWalletContract.GetCustomerWalletHistory` میآید) |
|
||||||
|
| `TokenNotificationService` | ثبت در DI؛ **هنوز به Layout وصل نشده** (SignalR برای invalidation توکن) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## نمودار جریان پرداخت
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
subgraph initiate [شروع پرداخت]
|
||||||
|
A1[Profile / Checkout]
|
||||||
|
A2[MagicWallet / ChargeDiscount]
|
||||||
|
A3[Discount Checkout]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph gateway [Zarinpal]
|
||||||
|
G[درگاه پرداخت]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph callback [PaymentCallback]
|
||||||
|
C1[Verify Package]
|
||||||
|
C2[Verify Magic]
|
||||||
|
C3[Verify Discount Wallet]
|
||||||
|
C4[Verify Discount Order]
|
||||||
|
end
|
||||||
|
|
||||||
|
A1 --> G
|
||||||
|
A2 --> G
|
||||||
|
A3 --> G
|
||||||
|
G --> callback
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## فهرست gRPC Contractهای فعال
|
||||||
|
|
||||||
|
| Contract | Wrapper اصلی |
|
||||||
|
|----------|----------------|
|
||||||
|
| `UserContract` | `AuthService`, صفحات Profile/Register |
|
||||||
|
| `UserAddressContract` | Profile, Checkout pages |
|
||||||
|
| `CityContract` | Address dialogs |
|
||||||
|
| `PackageContract` | `PackageService`, PaymentCallback, Checkout |
|
||||||
|
| `UserWalletContract` | `WalletService` |
|
||||||
|
| `UserOrderContract` | `OrderService`, CheckoutSummary |
|
||||||
|
| `UserCartsContract` | `CartService` |
|
||||||
|
| `ProductsContract` | `ProductService` |
|
||||||
|
| `CategoryContract` | `CategoryService` |
|
||||||
|
| `DiscountProductContract` / `DiscountCategoryContract` | `DiscountProductService` |
|
||||||
|
| `DiscountShoppingCartContract` | `DiscountCartService` |
|
||||||
|
| `DiscountOrderContract` | `DiscountOrderService` |
|
||||||
|
| `ClubMembershipContract` | `ClubMembershipService`, Dialog |
|
||||||
|
| `OtpTokenContract` | Club contract dialog |
|
||||||
|
| `ConfigurationContract` | `ClubConfigurationService` |
|
||||||
|
| `NetworkMembershipContract` | `NetworkMembershipService` |
|
||||||
|
| `CommissionContract` | `CommissionService` |
|
||||||
|
| `BlogPostContract` | `BlogPostService` |
|
||||||
|
| `BlogCategoryContract` | `BlogCategoryService` |
|
||||||
|
| `SitePageSettingsContract` | `SitePageSettingsService` |
|
||||||
|
| `AppVersionContract` | `AppVersionService` |
|
||||||
|
| `ImageResolverContract` | `ImageCacheService` |
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# Docs moved to totalDoc
|
||||||
|
|
||||||
|
See [totalDoc/INDEX.md](../../totalDoc/INDEX.md) for all documentation.
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
---
|
||||||
|
# FrontOffice (Blazor Server) production.
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: frontoffice
|
||||||
|
namespace: default
|
||||||
|
labels:
|
||||||
|
app: frontoffice
|
||||||
|
environment: production
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: frontoffice
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: frontoffice
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: frontoffice
|
||||||
|
image: 194.5.195.53:30080/admin/frontoffice:prod
|
||||||
|
imagePullPolicy: Always
|
||||||
|
ports:
|
||||||
|
- containerPort: 80
|
||||||
|
name: http
|
||||||
|
env:
|
||||||
|
- name: ASPNETCORE_ENVIRONMENT
|
||||||
|
value: "Production"
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
memory: "256Mi"
|
||||||
|
cpu: "250m"
|
||||||
|
limits:
|
||||||
|
memory: "512Mi"
|
||||||
|
cpu: "500m"
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: gitea-registry-secret
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: frontoffice-svc
|
||||||
|
namespace: default
|
||||||
|
labels:
|
||||||
|
app: frontoffice
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: frontoffice
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
targetPort: 80
|
||||||
|
name: http
|
||||||
|
type: ClusterIP
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
---
|
||||||
|
# FrontOffice (Blazor Server) staging.
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: frontoffice
|
||||||
|
namespace: default
|
||||||
|
labels:
|
||||||
|
app: frontoffice
|
||||||
|
environment: staging
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: frontoffice
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: frontoffice
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: frontoffice
|
||||||
|
image: 194.5.195.53:30080/admin/frontoffice:latest
|
||||||
|
imagePullPolicy: Always
|
||||||
|
ports:
|
||||||
|
- containerPort: 80
|
||||||
|
name: http
|
||||||
|
env:
|
||||||
|
- name: ASPNETCORE_ENVIRONMENT
|
||||||
|
value: "Staging"
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
memory: "256Mi"
|
||||||
|
cpu: "250m"
|
||||||
|
limits:
|
||||||
|
memory: "512Mi"
|
||||||
|
cpu: "500m"
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: gitea-registry-secret
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: frontoffice-svc
|
||||||
|
namespace: default
|
||||||
|
labels:
|
||||||
|
app: frontoffice
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: frontoffice
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
targetPort: 80
|
||||||
|
name: http
|
||||||
|
type: ClusterIP
|
||||||
@@ -5,23 +5,32 @@ using Grpc.Net.Client;
|
|||||||
using MudBlazor.Services;
|
using MudBlazor.Services;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using FrontOffice.BFF.Category.Protobuf.Protos.Category;
|
// Updated imports to use CMS protobuf instead of FrontOffice.BFF
|
||||||
using FrontOffice.BFF.Package.Protobuf.Protos.Package;
|
using CMSMicroservice.Protobuf.Protos.Category;
|
||||||
using FrontOffice.BFF.Transaction.Protobuf.Protos.Transaction;
|
using CMSMicroservice.Protobuf.Protos.City;
|
||||||
using FrontOffice.BFF.User.Protobuf.Protos.User;
|
using CMSMicroservice.Protobuf.Protos.Package;
|
||||||
using FrontOffice.BFF.UserAddress.Protobuf.Protos.UserAddress;
|
using CMSMicroservice.Protobuf.Protos.Products;
|
||||||
using FrontOffice.BFF.UserOrder.Protobuf.Protos.UserOrder;
|
using CMSMicroservice.Protobuf.Protos.Transactions;
|
||||||
using FrontOffice.BFF.UserWallet.Protobuf.Protos.UserWallet;
|
using CMSMicroservice.Protobuf.Protos.User;
|
||||||
using FrontOffice.BFF.ShopingCart.Protobuf.Protos.ShopingCart;
|
using CMSMicroservice.Protobuf.Protos.UserCarts;
|
||||||
// New Proto imports
|
using CMSMicroservice.Protobuf.Protos.UserOrder;
|
||||||
using FrontOffice.BFF.ClubMembership.Protobuf.Protos.ClubMembership;
|
using CMSMicroservice.Protobuf.Protos.UserWallet;
|
||||||
using FrontOffice.BFF.Commission.Protobuf.Protos.Commission;
|
using CMSMicroservice.Protobuf.Protos.UserWalletHistory;
|
||||||
using FrontOffice.BFF.NetworkMembership.Protobuf.Protos.NetworkMembership;
|
using CMSMicroservice.Protobuf.Protos.UserAddress;
|
||||||
using FrontOffice.BFF.DiscountShop.Protobuf.Protos.DiscountShop;
|
using CMSMicroservice.Protobuf.Protos.Configuration;
|
||||||
using FrontOffice.BFF.City.Protobuf;
|
using CMSMicroservice.Protobuf.Protos.NetworkMembership;
|
||||||
using FrontOffice.BFF.Configuration.Protobuf.Protos.Configuration;
|
using CMSMicroservice.Protobuf.Protos.Commission;
|
||||||
using FrontOffice.BFF.Configuration.Protobuf.Protos.AppVersion;
|
using CMSMicroservice.Protobuf.Protos.AppVersion;
|
||||||
|
using CMSMicroservice.Protobuf.Protos.SitePage;
|
||||||
|
using CMSMicroservice.Protobuf.Protos.SitePageSettings;
|
||||||
|
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;
|
using FrontOffice.Main.Utilities;
|
||||||
|
using FrontOffice.Main.Utilities.Seo;
|
||||||
|
|
||||||
namespace Microsoft.Extensions.DependencyInjection;
|
namespace Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
@@ -48,6 +57,7 @@ public static class ConfigureServices
|
|||||||
services.AddSingleton<UserAuthInfo>();
|
services.AddSingleton<UserAuthInfo>();
|
||||||
services.AddScoped<AuthService>();
|
services.AddScoped<AuthService>();
|
||||||
services.AddScoped<AuthDialogService>();
|
services.AddScoped<AuthDialogService>();
|
||||||
|
services.AddScoped<GuestActionGate>();
|
||||||
// Storefront services
|
// Storefront services
|
||||||
services.AddScoped<CartService>();
|
services.AddScoped<CartService>();
|
||||||
services.AddScoped<ProductService>();
|
services.AddScoped<ProductService>();
|
||||||
@@ -64,6 +74,20 @@ public static class ConfigureServices
|
|||||||
services.AddScoped<CommissionService>();
|
services.AddScoped<CommissionService>();
|
||||||
// App Version Service for cache invalidation
|
// App Version Service for cache invalidation
|
||||||
services.AddScoped<AppVersionService>();
|
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>();
|
||||||
|
// Site Page Settings Service (simplified page management)
|
||||||
|
services.AddScoped<SitePageSettingsService>();
|
||||||
// Device detection: very light, dependency-free
|
// Device detection: very light, dependency-free
|
||||||
services.AddTransient<IDeviceDetector, DeviceDetector>();
|
services.AddTransient<IDeviceDetector, DeviceDetector>();
|
||||||
// PDF generation (Chromium only)
|
// PDF generation (Chromium only)
|
||||||
@@ -72,58 +96,98 @@ public static class ConfigureServices
|
|||||||
// SignalR Token Notification Service
|
// SignalR Token Notification Service
|
||||||
services.AddScoped<TokenNotificationService>();
|
services.AddScoped<TokenNotificationService>();
|
||||||
|
|
||||||
|
// SEO
|
||||||
|
services.AddScoped<SeoMetadataProvider>();
|
||||||
|
services.AddScoped<SitemapGenerator>();
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IServiceCollection AddGrpcServices(this IServiceCollection services, IConfiguration configuration)
|
public static IServiceCollection AddGrpcServices(this IServiceCollection services, IConfiguration configuration)
|
||||||
{
|
{
|
||||||
var baseUrl = configuration["GwUrl"];
|
var baseUrl = ResolveGatewayUrl(configuration)
|
||||||
|
?? throw new InvalidOperationException("Gateway URL is missing. Set GW_URL or GwUrl.");
|
||||||
|
|
||||||
// Register optimized HttpClient for gRPC
|
var isHttp = baseUrl.StartsWith("http://", StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
// When the base URL is plain HTTP (e.g. in-cluster http://cms-svc:8080), we must force HTTP/1.1
|
||||||
|
// so that GrpcChannel does not upgrade to h2c and trigger HTTP_1_1_REQUIRED from Kestrel.
|
||||||
|
// For HTTPS the default negotiation is fine.
|
||||||
services.AddScoped(sp =>
|
services.AddScoped(sp =>
|
||||||
{
|
{
|
||||||
var handler = new HttpClientHandler
|
HttpMessageHandler inner = isHttp
|
||||||
{
|
? new SocketsHttpHandler
|
||||||
MaxConnectionsPerServer = 10,
|
{
|
||||||
AutomaticDecompression = System.Net.DecompressionMethods.GZip | System.Net.DecompressionMethods.Deflate
|
MaxConnectionsPerServer = 10,
|
||||||
};
|
AutomaticDecompression = System.Net.DecompressionMethods.GZip | System.Net.DecompressionMethods.Deflate
|
||||||
|
}
|
||||||
|
: new HttpClientHandler
|
||||||
|
{
|
||||||
|
MaxConnectionsPerServer = 10,
|
||||||
|
AutomaticDecompression = System.Net.DecompressionMethods.GZip | System.Net.DecompressionMethods.Deflate
|
||||||
|
};
|
||||||
|
|
||||||
return new HttpClient(new GrpcWebHandler(GrpcWebMode.GrpcWeb, handler))
|
return new HttpClient(new GrpcWebHandler(GrpcWebMode.GrpcWeb, inner))
|
||||||
{
|
{
|
||||||
Timeout = TimeSpan.FromMinutes(10),
|
Timeout = TimeSpan.FromMinutes(10),
|
||||||
BaseAddress = new Uri(baseUrl)
|
BaseAddress = new Uri(baseUrl),
|
||||||
|
DefaultRequestVersion = isHttp ? System.Net.HttpVersion.Version11 : System.Net.HttpVersion.Version20,
|
||||||
|
DefaultVersionPolicy = isHttp
|
||||||
|
? System.Net.Http.HttpVersionPolicy.RequestVersionExact
|
||||||
|
: System.Net.Http.HttpVersionPolicy.RequestVersionOrHigher
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// Register gRPC clients with authentication
|
// Register gRPC clients with authentication - Updated for CMS
|
||||||
services.AddScoped(CreateAuthenticatedClient<PackageContract.PackageContractClient>);
|
services.AddScoped(CreateAuthenticatedClient<CMSMicroservice.Protobuf.Protos.Package.PackageContract.PackageContractClient>);
|
||||||
services.AddScoped(CreateAuthenticatedClient<UserContract.UserContractClient>);
|
services.AddScoped(CreateAuthenticatedClient<CMSMicroservice.Protobuf.Protos.User.UserContract.UserContractClient>);
|
||||||
services.AddScoped(CreateAuthenticatedClient<UserAddressContract.UserAddressContractClient>);
|
services.AddScoped(CreateAuthenticatedClient<CMSMicroservice.Protobuf.Protos.UserOrder.UserOrderContract.UserOrderContractClient>);
|
||||||
services.AddScoped(CreateAuthenticatedClient<UserOrderContract.UserOrderContractClient>);
|
services.AddScoped(CreateAuthenticatedClient<CMSMicroservice.Protobuf.Protos.UserWallet.UserWalletContract.UserWalletContractClient>);
|
||||||
services.AddScoped(CreateAuthenticatedClient<UserWalletContract.UserWalletContractClient>);
|
services.AddScoped(CreateAuthenticatedClient<CMSMicroservice.Protobuf.Protos.Category.CategoryContract.CategoryContractClient>);
|
||||||
services.AddScoped(CreateAuthenticatedClient<CategoryContract.CategoryContractClient>);
|
services.AddScoped(CreateAuthenticatedClient<CMSMicroservice.Protobuf.Protos.Products.ProductsContract.ProductsContractClient>);
|
||||||
// Products gRPC
|
services.AddScoped(CreateAuthenticatedClient<CMSMicroservice.Protobuf.Protos.Transactions.TransactionsContract.TransactionsContractClient>);
|
||||||
services.AddScoped(CreateAuthenticatedClient<FrontOffice.BFF.Products.Protobuf.Protos.Products.ProductsContract.ProductsContractClient>);
|
services.AddScoped(CreateAuthenticatedClient<CMSMicroservice.Protobuf.Protos.UserCarts.UserCartsContract.UserCartsContractClient>);
|
||||||
services.AddScoped(CreateAuthenticatedClient<TransactionContract.TransactionContractClient>);
|
services.AddScoped(CreateAuthenticatedClient<CMSMicroservice.Protobuf.Protos.City.CityContract.CityContractClient>);
|
||||||
services.AddScoped(CreateAuthenticatedClient<ShopingCartContract.ShopingCartContractClient>);
|
services.AddScoped(CreateAuthenticatedClient<CMSMicroservice.Protobuf.Protos.UserAddress.UserAddressContract.UserAddressContractClient>);
|
||||||
// New gRPC clients for Club, Network, Commission, DiscountShop
|
services.AddScoped(CreateAuthenticatedClient<CMSMicroservice.Protobuf.Protos.UserWalletHistory.UserWalletHistoryContract.UserWalletHistoryContractClient>);
|
||||||
services.AddScoped(CreateAuthenticatedClient<ClubMembershipContract.ClubMembershipContractClient>);
|
services.AddScoped(CreateAuthenticatedClient<CMSMicroservice.Protobuf.Protos.ClubMembership.ClubMembershipContract.ClubMembershipContractClient>);
|
||||||
services.AddScoped(CreateAuthenticatedClient<CommissionContract.CommissionContractClient>);
|
services.AddScoped(CreateAuthenticatedClient<CMSMicroservice.Protobuf.Protos.OtpToken.OtpTokenContract.OtpTokenContractClient>);
|
||||||
services.AddScoped(CreateAuthenticatedClient<ConfigurationContract.ConfigurationContractClient>);
|
services.AddScoped(CreateAuthenticatedClient<CMSMicroservice.Protobuf.Protos.Configuration.ConfigurationContract.ConfigurationContractClient>);
|
||||||
services.AddScoped(CreateAuthenticatedClient<NetworkMembershipContract.NetworkMembershipContractClient>);
|
services.AddScoped(CreateAuthenticatedClient<CMSMicroservice.Protobuf.Protos.NetworkMembership.NetworkMembershipContract.NetworkMembershipContractClient>);
|
||||||
services.AddScoped(CreateAuthenticatedClient<DiscountShopContract.DiscountShopContractClient>);
|
services.AddScoped(CreateAuthenticatedClient<CMSMicroservice.Protobuf.Protos.Commission.CommissionContract.CommissionContractClient>);
|
||||||
services.AddScoped(CreateAuthenticatedClient<CityContract.CityContractClient>);
|
services.AddScoped(CreateAuthenticatedClient<CMSMicroservice.Protobuf.Protos.AppVersion.AppVersionContract.AppVersionContractClient>);
|
||||||
services.AddScoped(CreateAuthenticatedClient<AppVersionContract.AppVersionContractClient>);
|
services.AddScoped(CreateAuthenticatedClient<CMSMicroservice.Protobuf.Protos.SitePage.SitePageContract.SitePageContractClient>);
|
||||||
|
services.AddScoped(CreateAuthenticatedClient<SitePageSettingsContract.SitePageSettingsContractClient>);
|
||||||
|
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;
|
return services;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string? ResolveGatewayUrl(IConfiguration configuration)
|
||||||
|
{
|
||||||
|
var envUrl = configuration["GW_URL"];
|
||||||
|
if (!string.IsNullOrWhiteSpace(envUrl))
|
||||||
|
return envUrl.TrimEnd('/');
|
||||||
|
|
||||||
|
return configuration["GwUrl"]?.TrimEnd('/');
|
||||||
|
}
|
||||||
|
|
||||||
private static TClient CreateAuthenticatedClient<TClient>(IServiceProvider sp)
|
private static TClient CreateAuthenticatedClient<TClient>(IServiceProvider sp)
|
||||||
where TClient : class
|
where TClient : class
|
||||||
{
|
{
|
||||||
var httpClient = sp.GetRequiredService<HttpClient>();
|
var httpClient = sp.GetRequiredService<HttpClient>();
|
||||||
var localStorage = sp.GetRequiredService<ILocalStorageService>();
|
var localStorage = sp.GetRequiredService<ILocalStorageService>();
|
||||||
var baseUrl = httpClient.BaseAddress?.ToString() ?? throw new InvalidOperationException("Base URL not configured");
|
var baseUrl = httpClient.BaseAddress?.ToString() ?? throw new InvalidOperationException("Base URL not configured");
|
||||||
|
var isHttps = baseUrl.StartsWith("https://", StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
var credentials = CallCredentials.FromInterceptor(async (context, metadata) =>
|
var credentials = CallCredentials.FromInterceptor(async (context, metadata) =>
|
||||||
{
|
{
|
||||||
@@ -143,10 +207,14 @@ public static class ConfigureServices
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var channelCredentials = isHttps
|
||||||
|
? ChannelCredentials.Create(new SslCredentials(), credentials)
|
||||||
|
: ChannelCredentials.Create(ChannelCredentials.Insecure, credentials);
|
||||||
|
|
||||||
var channel = GrpcChannel.ForAddress(baseUrl, new GrpcChannelOptions
|
var channel = GrpcChannel.ForAddress(baseUrl, new GrpcChannelOptions
|
||||||
{
|
{
|
||||||
UnsafeUseInsecureChannelCallCredentials = true,
|
UnsafeUseInsecureChannelCallCredentials = !isHttps,
|
||||||
Credentials = ChannelCredentials.Create(new SslCredentials(), credentials),
|
Credentials = channelCredentials,
|
||||||
HttpClient = httpClient,
|
HttpClient = httpClient,
|
||||||
MaxReceiveMessageSize = 1000 * 1024 * 1024, // 1 GB
|
MaxReceiveMessageSize = 1000 * 1024 * 1024, // 1 GB
|
||||||
MaxSendMessageSize = 1000 * 1024 * 1024 // 1 GB
|
MaxSendMessageSize = 1000 * 1024 * 1024 // 1 GB
|
||||||
|
|||||||
@@ -20,6 +20,10 @@ FROM 194.5.195.53:32082/dotnet/aspnet:9.0 AS runtime
|
|||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY --from=build /app/publish .
|
COPY --from=build /app/publish .
|
||||||
|
|
||||||
|
# Trust the staging-ca so server-side gRPC calls to https://cms.se.kbs1.ir succeed without PartialChain
|
||||||
|
COPY ["FrontOffice.Main/staging-ca.crt", "/usr/local/share/ca-certificates/staging-ca.crt"]
|
||||||
|
RUN update-ca-certificates
|
||||||
|
|
||||||
ENV ASPNETCORE_URLS=http://+:80
|
ENV ASPNETCORE_URLS=http://+:80
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
@@ -10,29 +10,9 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="DateTimeConverterCL" Version="1.0.0" />
|
<PackageReference Include="DateTimeConverterCL" Version="1.0.0" />
|
||||||
<PackageReference Include="Foursat.FrontOffice.BFF.City.Protobuf" Version="0.0.2" />
|
<!-- Replace all FrontOffice.BFF protobuf packages with CMS protobuf -->
|
||||||
<PackageReference Include="Foursat.FrontOffice.BFF.ClubMembership.Protobuf" Version="0.0.4" />
|
<PackageReference Include="Foursat.CMSMicroservice.Protobuf" Version="0.0.204" />
|
||||||
<PackageReference Include="Foursat.FrontOffice.BFF.Commission.Protobuf" Version="0.0.6" />
|
<!-- <ProjectReference Include="../../../CMS/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj" />-->
|
||||||
<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" /> -->
|
|
||||||
<PackageReference Include="MudBlazor" Version="8.14.0" />
|
<PackageReference Include="MudBlazor" Version="8.14.0" />
|
||||||
<PackageReference Include="Blazored.LocalStorage" Version="4.5.0" />
|
<PackageReference Include="Blazored.LocalStorage" Version="4.5.0" />
|
||||||
<PackageReference Include="Mapster" Version="7.4.0" />
|
<PackageReference Include="Mapster" Version="7.4.0" />
|
||||||
@@ -76,12 +56,6 @@
|
|||||||
<Content Include="..\.dockerignore">
|
<Content Include="..\.dockerignore">
|
||||||
<Link>.dockerignore</Link>
|
<Link>.dockerignore</Link>
|
||||||
</Content>
|
</Content>
|
||||||
<Content Remove="Pages\Package\Packages.razor" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Compile Remove="Pages\Package\Packages.razor.cs" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -2,24 +2,13 @@
|
|||||||
<configuration>
|
<configuration>
|
||||||
<packageSources>
|
<packageSources>
|
||||||
<clear />
|
<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" />
|
<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>
|
</packageSources>
|
||||||
<packageSourceCredentials>
|
<packageSourceCredentials>
|
||||||
<Nexus>
|
<Nexus>
|
||||||
<add key="Username" value="admin" />
|
<add key="Username" value="admin" />
|
||||||
<add key="ClearTextPassword" value="87zH26nbqT" />
|
<add key="ClearTextPassword" value="87zH26nbqT" />
|
||||||
</Nexus>
|
</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>
|
</packageSourceCredentials>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
|||||||
@@ -1,192 +1,262 @@
|
|||||||
@attribute [Route(RouteConstants.About.Index)]
|
@attribute [Route(RouteConstants.About.Index)]
|
||||||
@inject NavigationManager Navigation
|
@inject NavigationManager Navigation
|
||||||
|
@inject SitePageSettingsService PageSettingsService
|
||||||
|
|
||||||
<PageTitle>درباره ما | کارا بازار سلامت</PageTitle>
|
<PageTitle>درباره ما | کارا بازار سلامت</PageTitle>
|
||||||
|
|
||||||
<!-- Hero Section -->
|
@if (_loading)
|
||||||
<section class="about-hero-section py-16">
|
{
|
||||||
<MudContainer MaxWidth="MaxWidth.Large">
|
<MudContainer MaxWidth="MaxWidth.Large" Class="py-16">
|
||||||
<MudGrid Justify="Justify.Center" Spacing="4">
|
<LoadingState />
|
||||||
<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>
|
|
||||||
</MudContainer>
|
</MudContainer>
|
||||||
</section>
|
}
|
||||||
|
else
|
||||||
<MudStack Spacing="8">
|
{
|
||||||
|
<!-- Hero Section -->
|
||||||
<!-- Mission & Vision -->
|
<section class="about-hero-section py-16">
|
||||||
<section class="py-12">
|
|
||||||
<MudContainer MaxWidth="MaxWidth.Large">
|
<MudContainer MaxWidth="MaxWidth.Large">
|
||||||
<MudText Typo="Typo.h3" Align="Align.Center" Class="mb-8">چشمانداز و مأموریت ما</MudText>
|
<MudGrid Justify="Justify.Center" Spacing="4">
|
||||||
<MudGrid Spacing="4" Justify="Justify.Center">
|
|
||||||
<MudItem xs="12" md="6">
|
<MudItem xs="12" md="6">
|
||||||
<MudPaper Elevation="3" Class="pa-6 text-center h-100">
|
<MudChip T="string" Color="Color.Secondary" Variant="Variant.Filled" Class="mb-2">درباره کارا بازار سلامت</MudChip>
|
||||||
<MudIcon Icon="@Icons.Material.Filled.Visibility" Size="Size.Large" Color="Color.Primary" Class="mb-4" />
|
<MudStack Spacing="3">
|
||||||
<MudText Typo="Typo.h5" Class="mb-3">چشمانداز</MudText>
|
<MudText Typo="Typo.h2" Class="mb-3">
|
||||||
<MudText Typo="Typo.body1" Class="mud-text-secondary">
|
@(_page?.HeroTitle ?? "پلتفرم هوشمند تیمسازی و مدیریت فروش")
|
||||||
تبدیل شدن به پیشروترین پلتفرم مدیریت تیمهای فروش در منطقه، با ارائه راهکارهای هوشمند و کاربرپسند برای کسبوکارهای کوچک و بزرگ.
|
|
||||||
</MudText>
|
</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>
|
||||||
<MudItem xs="12" md="6">
|
<MudItem xs="12" md="6">
|
||||||
<MudPaper Elevation="3" Class="pa-6 text-center h-100">
|
<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);">
|
||||||
<MudIcon Icon="@Icons.Material.Filled.TrackChanges" Size="Size.Large" Color="Color.Success" Class="mb-4" />
|
<AppImage Path="@(_page?.HeroImagePath ?? "images/team-work.jpg")"
|
||||||
<MudText Typo="Typo.h5" Class="mb-3">مأموریت</MudText>
|
Alt="جلسه تیم فروش"
|
||||||
<MudText Typo="Typo.body1" Class="mud-text-secondary">
|
ObjectFit="ObjectFit.Cover"
|
||||||
توانمندسازی کسبوکارها از طریق فناوریهای نوین، سادهسازی فرآیندهای پیچیده و ایجاد کارا بازار سلامتهای جدید برای رشد و توسعه پایدار.
|
ObjectPosition="ObjectPosition.Center"
|
||||||
</MudText>
|
Style="width:100%"
|
||||||
|
Class="rounded-xl" />
|
||||||
</MudPaper>
|
</MudPaper>
|
||||||
</MudItem>
|
</MudItem>
|
||||||
</MudGrid>
|
</MudGrid>
|
||||||
</MudContainer>
|
</MudContainer>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Company Values -->
|
<MudStack Spacing="8">
|
||||||
<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>
|
|
||||||
|
|
||||||
<!-- Team Section -->
|
<!-- Mission & Vision -->
|
||||||
<section class="pb-12 bg-grey-50">
|
<section class="py-12">
|
||||||
<MudContainer MaxWidth="MaxWidth.Large">
|
<MudContainer MaxWidth="MaxWidth.Large">
|
||||||
<MudText Typo="Typo.h3" Align="Align.Center" Class="mb-8">تیم ما</MudText>
|
<MudText Typo="Typo.h3" Align="Align.Center" Class="mb-8">چشمانداز و مأموریت ما</MudText>
|
||||||
<MudGrid Spacing="4" Justify="Justify.Center">
|
<MudGrid Spacing="4" Justify="Justify.Center">
|
||||||
<MudItem xs="12" sm="6" md="4">
|
<MudItem xs="12" md="6">
|
||||||
<MudPaper Elevation="3" Class="pa-4 text-center">
|
<MudPaper Elevation="2" Class="pa-6 text-center h-100">
|
||||||
<MudAvatar Size="Size.Large" Class="mb-3">
|
<MudIcon Icon="@Icons.Material.Filled.Visibility" Size="Size.Large" Color="Color.Primary" Class="mb-4" />
|
||||||
<MudImage ObjectFit="ObjectFit.Cover"
|
<MudText Typo="Typo.h5" Class="mb-3">
|
||||||
ObjectPosition="ObjectPosition.Center"
|
@(_settings?.VisionTitle ?? "چشمانداز")
|
||||||
Src="images/avatar3.jpg" />
|
</MudText>
|
||||||
</MudAvatar>
|
<MudText Typo="Typo.body1" Class="mud-text-secondary">
|
||||||
<MudText Typo="Typo.h6" Class="mb-1">علی رضایی</MudText>
|
@(_settings?.VisionText ?? "تبدیل شدن به پیشروترین پلتفرم مدیریت تیمهای فروش در منطقه، با ارائه راهکارهای هوشمند و کاربرپسند برای کسبوکارهای کوچک و بزرگ.")
|
||||||
<MudText Typo="Typo.body2" Class="mud-text-secondary mb-2">مدیرعامل</MudText>
|
</MudText>
|
||||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">
|
</MudPaper>
|
||||||
بیش از ۱۰ سال تجربه در حوزه فناوری و مدیریت کسبوکار
|
</MudItem>
|
||||||
</MudText>
|
<MudItem xs="12" md="6">
|
||||||
</MudPaper>
|
<MudPaper Elevation="2" Class="pa-6 text-center h-100">
|
||||||
</MudItem>
|
<MudIcon Icon="@Icons.Material.Filled.TrackChanges" Size="Size.Large" Color="Color.Success" Class="mb-4" />
|
||||||
<MudItem xs="12" sm="6" md="4">
|
<MudText Typo="Typo.h5" Class="mb-3">
|
||||||
<MudPaper Elevation="3" Class="pa-4 text-center">
|
@(_settings?.MissionTitle ?? "مأموریت")
|
||||||
<MudAvatar Size="Size.Large" Class="mb-3">
|
</MudText>
|
||||||
<MudImage ObjectFit="ObjectFit.Cover"
|
<MudText Typo="Typo.body1" Class="mud-text-secondary">
|
||||||
ObjectPosition="ObjectPosition.Center"
|
@(_settings?.MissionText ?? "توانمندسازی کسبوکارها از طریق فناوریهای نوین، سادهسازی فرآیندهای پیچیده و ایجاد فرصتهای جدید برای رشد و توسعه پایدار.")
|
||||||
Src="images/avatar4.jpg" />
|
</MudText>
|
||||||
</MudAvatar>
|
</MudPaper>
|
||||||
<MudText Typo="Typo.h6" Class="mb-1">مریم احمدی</MudText>
|
</MudItem>
|
||||||
<MudText Typo="Typo.body2" Class="mud-text-secondary mb-2">مدیر محصول</MudText>
|
</MudGrid>
|
||||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">
|
</MudContainer>
|
||||||
متخصص در طراحی تجربه کاربری و توسعه محصول
|
</section>
|
||||||
</MudText>
|
|
||||||
</MudPaper>
|
<!-- Company Values -->
|
||||||
</MudItem>
|
<section class="py-12 bg-grey-50">
|
||||||
<MudItem xs="12" sm="6" md="4">
|
<MudContainer MaxWidth="MaxWidth.Large">
|
||||||
<MudPaper Elevation="3" Class="pa-4 text-center">
|
<MudText Typo="Typo.h3" Align="Align.Center" Class="mb-8">ارزشهای ما</MudText>
|
||||||
<MudAvatar Size="Size.Large" Class="mb-3">
|
<MudGrid Spacing="3" Justify="Justify.Center">
|
||||||
<MudImage ObjectFit="ObjectFit.Cover"
|
@if (_valueImages.Any())
|
||||||
ObjectPosition="ObjectPosition.Center"
|
{
|
||||||
Src="images/avatar5.jpg" />
|
@foreach (var value in _valueImages)
|
||||||
</MudAvatar>
|
{
|
||||||
<MudText Typo="Typo.h6" Class="mb-1">حسن کریمی</MudText>
|
<MudItem xs="12" sm="6" md="4">
|
||||||
<MudText Typo="Typo.body2" Class="mud-text-secondary mb-2">مدیر فنی</MudText>
|
<MudPaper Elevation="2" Class="pa-4 rounded-xl text-center">
|
||||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">
|
@if (!string.IsNullOrWhiteSpace(value.IconName))
|
||||||
مهندس نرمافزار با تخصص در معماری سیستمهای توزیعشده
|
{
|
||||||
</MudText>
|
<MudIcon Icon="@($"Icons.Material.Filled.{value.IconName}")" Size="Size.Large" Color="Color.Primary" Class="mb-3" />
|
||||||
</MudPaper>
|
}
|
||||||
</MudItem>
|
<MudText Typo="Typo.h6" Class="mb-2">@value.Title</MudText>
|
||||||
</MudGrid>
|
@if (!string.IsNullOrWhiteSpace(value.Description))
|
||||||
</MudContainer>
|
{
|
||||||
</section>
|
<MudText Typo="Typo.body2" Class="mud-text-secondary">
|
||||||
</MudStack>
|
@value.Description
|
||||||
|
</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 (_teamImages.Any())
|
||||||
|
{
|
||||||
|
@foreach (var member in _teamImages)
|
||||||
|
{
|
||||||
|
<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.Description))
|
||||||
|
{
|
||||||
|
<MudText Typo="Typo.caption" Class="mud-text-secondary">
|
||||||
|
@member.Description
|
||||||
|
</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,47 @@
|
|||||||
|
using FrontOffice.Main.Utilities;
|
||||||
|
|
||||||
namespace FrontOffice.Main.Pages;
|
namespace FrontOffice.Main.Pages;
|
||||||
|
|
||||||
public partial class About
|
public partial class About
|
||||||
{
|
{
|
||||||
private void ScrollToContact()
|
private bool _loading = true;
|
||||||
|
private PageSettingsDto? _page;
|
||||||
|
private AboutSettings? _settings;
|
||||||
|
private List<PageSettingsImageDto> _valueImages = new();
|
||||||
|
private List<PageSettingsImageDto> _teamImages = new();
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
// TODO: Implement smooth scroll to contact section
|
try
|
||||||
|
{
|
||||||
|
_page = await PageSettingsService.GetPageAsync("about");
|
||||||
|
|
||||||
|
if (_page != null)
|
||||||
|
{
|
||||||
|
_settings = _page.GetSettings<AboutSettings>();
|
||||||
|
_valueImages = _page.GetImages("values");
|
||||||
|
_teamImages = _page.GetImages("team");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Fallback: page remains null → hardcoded content will render
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class AboutSettings
|
||||||
|
{
|
||||||
|
public string? VisionTitle { get; set; }
|
||||||
|
public string? VisionText { get; set; }
|
||||||
|
public string? VisionIcon { get; set; }
|
||||||
|
public string? MissionTitle { get; set; }
|
||||||
|
public string? MissionText { get; set; }
|
||||||
|
public string? MissionIcon { get; set; }
|
||||||
|
public string? ValuesTitle { get; set; }
|
||||||
|
public string? TeamTitle { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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>
|
<PageTitle>تکمیل خرید</PageTitle>
|
||||||
|
|
||||||
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
||||||
|
<PageHeader Title="تکمیل خرید" BackHref="@RouteConstants.Package.List" />
|
||||||
<MudGrid Spacing="4">
|
<MudGrid Spacing="4">
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<MudItem xs="12">
|
<MudItem xs="12">
|
||||||
<MudPaper Elevation="4" Class="pa-6">
|
<MudPaper Elevation="2" Class="pa-4 rounded-lg">
|
||||||
<MudStack Spacing="3">
|
<MudStack Spacing="3">
|
||||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="3">
|
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="3">
|
||||||
<MudIcon Icon="@Icons.Material.Filled.ShoppingCart" Size="Size.Large" Color="Color.Primary" />
|
<MudIcon Icon="@Icons.Material.Filled.ShoppingCart" Size="Size.Large" Color="Color.Primary" />
|
||||||
@@ -23,9 +24,14 @@
|
|||||||
<MudItem xs="12" md="8">
|
<MudItem xs="12" md="8">
|
||||||
<MudStack>
|
<MudStack>
|
||||||
<!-- Package Details -->
|
<!-- Package Details -->
|
||||||
<MudPaper Elevation="4" Class="pa-6">
|
<MudPaper Elevation="2" Class="pa-4 rounded-lg">
|
||||||
<MudText Typo="Typo.h5" Class="mb-4">جزئیات پکیج</MudText>
|
<MudText Typo="Typo.h5" Class="mb-4">جزئیات پکیج</MudText>
|
||||||
|
|
||||||
|
@* ── G2: توضیح خرید پکیج (Q28) ── *@
|
||||||
|
<MudAlert Severity="Severity.Info" Variant="Variant.Text" Dense="true" Class="mb-3">
|
||||||
|
با خرید این پکیج، سقف پاداش هفتگی، ضریب کیفپول جادویی و دسترسی به فیچرهای اختصاصی برای شما فعال میشود.
|
||||||
|
</MudAlert>
|
||||||
|
|
||||||
@if (_selectedPackage != null)
|
@if (_selectedPackage != null)
|
||||||
{
|
{
|
||||||
<MudCard>
|
<MudCard>
|
||||||
@@ -33,9 +39,9 @@
|
|||||||
<MudGrid Spacing="3">
|
<MudGrid Spacing="3">
|
||||||
<MudItem xs="12" md="4">
|
<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);">
|
<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"
|
Alt="@_selectedPackage.Title"
|
||||||
Height="150"
|
ImgHeight="150"
|
||||||
ObjectFit="ObjectFit.Cover"
|
ObjectFit="ObjectFit.Cover"
|
||||||
ObjectPosition="ObjectPosition.Center"
|
ObjectPosition="ObjectPosition.Center"
|
||||||
Style="width:100%"
|
Style="width:100%"
|
||||||
@@ -65,80 +71,6 @@
|
|||||||
</MudStack>
|
</MudStack>
|
||||||
}
|
}
|
||||||
</MudPaper>
|
</MudPaper>
|
||||||
|
|
||||||
<!-- Address Selection -->
|
|
||||||
<MudPaper Elevation="4" Class="pa-6">
|
|
||||||
<MudText Typo="Typo.h5" Class="mb-4">انتخاب آدرس</MudText>
|
|
||||||
|
|
||||||
@if (_isLoadingAddresses)
|
|
||||||
{
|
|
||||||
<MudStack AlignItems="AlignItems.Center" Class="py-4">
|
|
||||||
<MudProgressCircular Color="Color.Primary" Indeterminate="true" Size="Size.Medium" />
|
|
||||||
<MudText Typo="Typo.body2" Class="mud-text-secondary mt-2">بارگذاری آدرسها...</MudText>
|
|
||||||
</MudStack>
|
|
||||||
}
|
|
||||||
else if (_addresses.Any())
|
|
||||||
{
|
|
||||||
<MudStack Spacing="2">
|
|
||||||
@foreach (var address in _addresses)
|
|
||||||
{
|
|
||||||
<MudPaper Outlined="@(_selectedAddress?.Id != address.Id)"
|
|
||||||
Elevation="@(_selectedAddress?.Id == address.Id ? 4 : 0)"
|
|
||||||
Class="pa-3 rounded-xl cursor-pointer"
|
|
||||||
Style="@(_selectedAddress?.Id == address.Id ? "border: 2px solid var(--mud-palette-primary);" : "")"
|
|
||||||
@onclick="() => SetAddressAsDefault(address.Id)">
|
|
||||||
<MudStack Spacing="1">
|
|
||||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
|
|
||||||
<MudCheckBox @bind-Value="address.IsDefault"
|
|
||||||
Disabled="true"
|
|
||||||
Color="Color.Primary" />
|
|
||||||
<MudText Typo="Typo.subtitle2">@(address.Title)</MudText>
|
|
||||||
@if (address.IsDefault)
|
|
||||||
{
|
|
||||||
<MudChip T="string" Color="Color.Success" Variant="Variant.Filled" Size="Size.Small">پیشفرض</MudChip>
|
|
||||||
}
|
|
||||||
@if (!address.IsDefault)
|
|
||||||
{
|
|
||||||
@if (_isSettingDefaultAddress && _settingDefaultAddressId == address.Id)
|
|
||||||
{
|
|
||||||
<MudProgressCircular Size="Size.Small" Color="Color.Primary" Indeterminate="true" />
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<MudButton Variant="Variant.Text"
|
|
||||||
Color="Color.Primary"
|
|
||||||
Size="Size.Small"
|
|
||||||
OnClick="() => SetAddressAsDefault(address.Id)">
|
|
||||||
تنظیم به عنوان پیشفرض
|
|
||||||
</MudButton>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</MudStack>
|
|
||||||
<MudStack Row="true">
|
|
||||||
<MudText Typo="Typo.body2" Class="mud-text-secondary">@(address.Address)</MudText>
|
|
||||||
<MudSpacer />
|
|
||||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">کد پستی: @(address.PostalCode)</MudText>
|
|
||||||
</MudStack>
|
|
||||||
</MudStack>
|
|
||||||
</MudPaper>
|
|
||||||
}
|
|
||||||
</MudStack>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<MudStack AlignItems="AlignItems.Center" Class="py-4">
|
|
||||||
<MudIcon Icon="@Icons.Material.Filled.LocationOff" Size="Size.Large" Color="Color.Default" />
|
|
||||||
<MudText Typo="Typo.body2" Class="mud-text-secondary mt-2">آدرسی ثبت نشده است.</MudText>
|
|
||||||
<MudButton Variant="Variant.Outlined"
|
|
||||||
Color="Color.Primary"
|
|
||||||
StartIcon="@Icons.Material.Filled.Add"
|
|
||||||
OnClick="() => Navigation.NavigateTo(RouteConstants.Profile.Index)"
|
|
||||||
Class="mt-2">
|
|
||||||
افزودن آدرس
|
|
||||||
</MudButton>
|
|
||||||
</MudStack>
|
|
||||||
}
|
|
||||||
</MudPaper>
|
|
||||||
</MudStack>
|
</MudStack>
|
||||||
</MudItem>
|
</MudItem>
|
||||||
|
|
||||||
@@ -149,7 +81,7 @@
|
|||||||
<MudItem xs="12" md="4">
|
<MudItem xs="12" md="4">
|
||||||
<MudStack Spacing="4">
|
<MudStack Spacing="4">
|
||||||
<!-- Discount Code Section -->
|
<!-- 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>
|
<MudText Typo="Typo.h5" Class="mb-4">کد تخفیف</MudText>
|
||||||
<MudStack Spacing="3">
|
<MudStack Spacing="3">
|
||||||
<MudTextField @bind-Value="_discountCode"
|
<MudTextField @bind-Value="_discountCode"
|
||||||
@@ -176,7 +108,7 @@
|
|||||||
<!-- Order Summary -->
|
<!-- Order Summary -->
|
||||||
@if (_selectedPackage != null)
|
@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>
|
<MudText Typo="Typo.h5" Class="mb-4">خلاصه سفارش</MudText>
|
||||||
|
|
||||||
<MudStack Spacing="3">
|
<MudStack Spacing="3">
|
||||||
@@ -206,15 +138,36 @@
|
|||||||
FullWidth="true"
|
FullWidth="true"
|
||||||
StartIcon="@Icons.Material.Filled.Payment"
|
StartIcon="@Icons.Material.Filled.Payment"
|
||||||
OnClick="ProcessPayment"
|
OnClick="ProcessPayment"
|
||||||
Disabled="@(!CanProceedToPayment || _isProcessingPayment)"
|
Disabled="@(!CanProceedToPayment || _isProcessingPayment || !_selectedPackage.SupportsDirectPurchase)"
|
||||||
Class="mt-2">
|
Class="mt-2">
|
||||||
@(_isProcessingPayment ? "در حال پردازش..." : "پرداخت آنلاین")
|
@(_isProcessingPayment ? "در حال پردازش..." : "پرداخت آنلاین")
|
||||||
</MudButton>
|
</MudButton>
|
||||||
|
|
||||||
|
@if (_selectedPackage.SupportsDayaPurchase)
|
||||||
|
{
|
||||||
|
<MudButton Variant="Variant.Filled"
|
||||||
|
Color="Color.Tertiary"
|
||||||
|
Size="Size.Large"
|
||||||
|
FullWidth="true"
|
||||||
|
StartIcon="@Icons.Material.Filled.Diamond"
|
||||||
|
OnClick="DayaLoanPayment"
|
||||||
|
Disabled="@(!CanProceedToPayment)"
|
||||||
|
Class="mt-2">
|
||||||
|
تأمین اعتبار الماسی دایا
|
||||||
|
</MudButton>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (!_selectedPackage.SupportsDirectPurchase && !_selectedPackage.SupportsDayaPurchase)
|
||||||
|
{
|
||||||
|
<MudAlert Severity="Severity.Warning" Dense="true" Class="mt-2">
|
||||||
|
این پکیج در حال حاضر قابل خرید نیست.
|
||||||
|
</MudAlert>
|
||||||
|
}
|
||||||
|
|
||||||
@if (!CanProceedToPayment)
|
@if (!CanProceedToPayment)
|
||||||
{
|
{
|
||||||
<MudText Typo="Typo.caption" Color="Color.Error" Align="Align.Center">
|
<MudText Typo="Typo.caption" Color="Color.Error" Align="Align.Center">
|
||||||
لطفاً پکیج و آدرس را انتخاب کنید.
|
لطفاً پکیج را انتخاب کنید.
|
||||||
</MudText>
|
</MudText>
|
||||||
}
|
}
|
||||||
</MudStack>
|
</MudStack>
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
using FrontOffice.BFF.Package.Protobuf.Protos.Package;
|
using CMSMicroservice.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 FrontOffice.Main.Utilities;
|
using FrontOffice.Main.Utilities;
|
||||||
using Google.Protobuf.WellKnownTypes;
|
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using Microsoft.JSInterop;
|
||||||
using MudBlazor;
|
using MudBlazor;
|
||||||
using Severity = MudBlazor.Severity;
|
using Severity = MudBlazor.Severity;
|
||||||
|
|
||||||
@@ -12,17 +9,12 @@ namespace FrontOffice.Main.Pages;
|
|||||||
|
|
||||||
public partial class Checkout
|
public partial class Checkout
|
||||||
{
|
{
|
||||||
|
[Inject] private PackageService PackageService { get; set; } = default!;
|
||||||
[Inject] private PackageContract.PackageContractClient PackageClient { get; set; } = default!;
|
[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!;
|
|
||||||
|
|
||||||
[Parameter] public long? PackageId { get; set; }
|
[Parameter] public long? PackageId { get; set; }
|
||||||
|
|
||||||
private Pack? _selectedPackage;
|
private Pack? _selectedPackage;
|
||||||
private List<GetAllUserAddressByFilterResponseModel> _addresses = new();
|
|
||||||
private GetAllUserAddressByFilterResponseModel? _selectedAddress;
|
|
||||||
private bool _isLoadingAddresses;
|
|
||||||
private bool _isProcessingPayment;
|
private bool _isProcessingPayment;
|
||||||
|
|
||||||
// Discount code
|
// Discount code
|
||||||
@@ -33,16 +25,11 @@ public partial class Checkout
|
|||||||
private long _discountAmount;
|
private long _discountAmount;
|
||||||
private long _finalPrice;
|
private long _finalPrice;
|
||||||
|
|
||||||
// Address management
|
private bool CanProceedToPayment => _selectedPackage != null;
|
||||||
private bool _isSettingDefaultAddress;
|
|
||||||
private long? _settingDefaultAddressId;
|
|
||||||
|
|
||||||
private bool CanProceedToPayment => _selectedPackage != null && _selectedAddress != null;
|
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
await LoadPackageDetails();
|
await LoadPackageDetails();
|
||||||
await LoadAddresses();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task LoadPackageDetails()
|
private async Task LoadPackageDetails()
|
||||||
@@ -51,15 +38,18 @@ public partial class Checkout
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var response = await PackageClient.GetPackageAsync(new() { Id = PackageId.Value });
|
var packages = await PackageService.GetAllPackagesAsync();
|
||||||
if (response != null)
|
var pkg = packages.FirstOrDefault(p => p.Id == PackageId.Value);
|
||||||
|
if (pkg != null)
|
||||||
{
|
{
|
||||||
_selectedPackage = new Pack(
|
_selectedPackage = new Pack(
|
||||||
Id: response.Id,
|
Id: pkg.Id,
|
||||||
Title: response.Title,
|
Title: pkg.Title,
|
||||||
Body: response.Description,
|
Body: pkg.Description,
|
||||||
Image: UrlUtility.DownloadUrl + response.ImagePath,
|
Image: pkg.ImageUrl,
|
||||||
Price: response.Price
|
Price: pkg.Price,
|
||||||
|
SupportsDirectPurchase: pkg.SupportsDirectPurchase,
|
||||||
|
SupportsDayaPurchase: pkg.SupportsDayaPurchase
|
||||||
);
|
);
|
||||||
_finalPrice = _selectedPackage.Price;
|
_finalPrice = _selectedPackage.Price;
|
||||||
}
|
}
|
||||||
@@ -71,60 +61,6 @@ public partial class Checkout
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task LoadAddresses()
|
|
||||||
{
|
|
||||||
_isLoadingAddresses = true;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var response = await UserAddressContract.GetAllUserAddressByFilterAsync(request: new());
|
|
||||||
if (response?.Models?.Any() == true)
|
|
||||||
{
|
|
||||||
_addresses = response.Models.ToList();
|
|
||||||
// Select default address if available
|
|
||||||
_selectedAddress = _addresses.FirstOrDefault(a => a.IsDefault) ?? _addresses.First();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_addresses = new List<GetAllUserAddressByFilterResponseModel>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Snackbar.Add($"خطا در بارگذاری آدرسها: {ex.Message}", Severity.Error);
|
|
||||||
_addresses = new List<GetAllUserAddressByFilterResponseModel>();
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_isLoadingAddresses = false;
|
|
||||||
await InvokeAsync(StateHasChanged);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task SetAddressAsDefault(long addressId)
|
|
||||||
{
|
|
||||||
if (_isSettingDefaultAddress) return;
|
|
||||||
|
|
||||||
_isSettingDefaultAddress = true;
|
|
||||||
_settingDefaultAddressId = addressId;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await UserAddressContract.SetAddressAsDefaultAsync(new() { Id = addressId });
|
|
||||||
await LoadAddresses(); // Reload addresses to reflect the change
|
|
||||||
Snackbar.Add("آدرس پیشفرض با موفقیت تغییر یافت.", Severity.Success);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Snackbar.Add($"خطا در تغییر آدرس پیشفرض: {ex.Message}", Severity.Error);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_isSettingDefaultAddress = false;
|
|
||||||
_settingDefaultAddressId = null;
|
|
||||||
await InvokeAsync(StateHasChanged);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task ApplyDiscountCode()
|
private async Task ApplyDiscountCode()
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(_discountCode))
|
if (string.IsNullOrWhiteSpace(_discountCode))
|
||||||
@@ -156,7 +92,7 @@ public partial class Checkout
|
|||||||
_finalPrice = _selectedPackage!.Price;
|
_finalPrice = _selectedPackage!.Price;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
_discountMessage = "خطا در اعمال کد تخفیف.";
|
_discountMessage = "خطا در اعمال کد تخفیف.";
|
||||||
_discountApplied = false;
|
_discountApplied = false;
|
||||||
@@ -172,12 +108,11 @@ public partial class Checkout
|
|||||||
|
|
||||||
private async Task ProcessPayment()
|
private async Task ProcessPayment()
|
||||||
{
|
{
|
||||||
Snackbar.Add("درگاه پرداخت متصل نیست! لطفا در زمان دیگری مجددا تلاش فرمایید!", Severity.Warning);
|
if (_isProcessingPayment) return;
|
||||||
return;
|
|
||||||
|
|
||||||
if (!CanProceedToPayment || _selectedPackage == null || _selectedAddress == null)
|
if (!CanProceedToPayment || _selectedPackage == null)
|
||||||
{
|
{
|
||||||
Snackbar.Add("لطفاً پکیج و آدرس را انتخاب کنید.", Severity.Warning);
|
Snackbar.Add("لطفاً پکیج را انتخاب کنید.", Severity.Warning);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,33 +120,26 @@ public partial class Checkout
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Step 1: Create payment request
|
var response = await PackageClient.CustomerPurchasePackageAsync(new CustomerPurchasePackageRequest
|
||||||
var paymentRequest = new PaymentRequestRequest
|
|
||||||
{
|
{
|
||||||
Amount = _finalPrice,
|
|
||||||
CallbackUrl = $"{Navigation.BaseUri}checkout/callback",
|
|
||||||
Description = $"خرید پکیج {_selectedPackage.Title}",
|
|
||||||
Currency = CurrencyEnum.Irt,
|
|
||||||
Type = TransactionTypeEnum.Real
|
|
||||||
};
|
|
||||||
|
|
||||||
var paymentResponse = await TransactionContract.PaymentRequestAsync(paymentRequest);
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(paymentResponse.PaymentGWUrl))
|
|
||||||
Snackbar.Add("آدرس درگاه پرداخت دریافت نشد.", Severity.Error);
|
|
||||||
|
|
||||||
// Step 2: Create user order
|
|
||||||
var orderRequest = new CreateNewUserOrderRequest
|
|
||||||
{
|
|
||||||
Price = _finalPrice,
|
|
||||||
PackageId = _selectedPackage.Id,
|
PackageId = _selectedPackage.Id,
|
||||||
PaymentStatus = false // Will be updated after payment verification
|
PurchaseMethod = PurchaseMethodEnum.PurchaseMethodGateway
|
||||||
};
|
});
|
||||||
|
|
||||||
var orderResponse = await UserOrderContract.CreateNewUserOrderAsync(orderRequest);
|
if (!response.Success)
|
||||||
|
{
|
||||||
|
Snackbar.Add(response.Message ?? "خطا در آغاز فرآیند پرداخت", Severity.Error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Step 3: Redirect to payment gateway
|
if (!string.IsNullOrEmpty(response.PaymentGatewayUrl))
|
||||||
Navigation.NavigateTo(paymentResponse.PaymentGWUrl);
|
{
|
||||||
|
Navigation.NavigateTo(response.PaymentGatewayUrl, forceLoad: true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Snackbar.Add("خطا در دریافت آدرس درگاه پرداخت", Severity.Error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -224,5 +152,17 @@ public partial class Checkout
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private record Pack(long Id, string Title, string Body, string Image, long Price);
|
private async Task DayaLoanPayment()
|
||||||
}
|
{
|
||||||
|
if (_selectedPackage == null)
|
||||||
|
{
|
||||||
|
Snackbar.Add("لطفاً پکیج را انتخاب کنید.", Severity.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var url = "https://dayadiamond.ir/profile/creditpurchase/?merchantcode=56146364";
|
||||||
|
await JSRuntime.InvokeVoidAsync("open", url, "_blank");
|
||||||
|
}
|
||||||
|
|
||||||
|
private record Pack(long Id, string Title, string Body, string Image, long Price, bool SupportsDirectPurchase = true, bool SupportsDayaPurchase = false);
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,7 +3,12 @@
|
|||||||
|
|
||||||
<MudPaper Elevation="2" Class="pa-4 rounded-lg">
|
<MudPaper Elevation="2" Class="pa-4 rounded-lg">
|
||||||
<MudText Typo="Typo.h6" Class="mb-3">فعالسازی یا تمدید عضویت</MudText>
|
<MudText Typo="Typo.h6" Class="mb-3">فعالسازی یا تمدید عضویت</MudText>
|
||||||
|
|
||||||
|
@* ── G7: راهنمای هزینه فعالسازی (Q28) ── *@
|
||||||
|
<MudAlert Severity="Severity.Info" Variant="Variant.Text" Dense="true" Class="mb-3" Icon="@Icons.Material.Filled.Info">
|
||||||
|
هزینه تخمینی بر اساس پکیج انتخابی محاسبه میشود. هر پکیج قیمت و شرایط فعالسازی متفاوتی دارد.
|
||||||
|
</MudAlert>
|
||||||
|
|
||||||
<MudForm @ref="_form" @bind-IsValid="_formIsValid">
|
<MudForm @ref="_form" @bind-IsValid="_formIsValid">
|
||||||
<MudGrid Spacing="2">
|
<MudGrid Spacing="2">
|
||||||
<MudItem xs="12" sm="6">
|
<MudItem xs="12" sm="6">
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
using FrontOffice.Main.Utilities;
|
using FrontOffice.Main.Utilities;
|
||||||
using FrontOffice.BFF.User.Protobuf.Protos.User;
|
using CMSMicroservice.Protobuf.Protos.User;
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
using MudBlazor;
|
using MudBlazor;
|
||||||
|
|
||||||
@@ -10,6 +10,7 @@ public partial class ActivationSection : ComponentBase
|
|||||||
[Inject] private ClubMembershipService ClubService { get; set; } = default!;
|
[Inject] private ClubMembershipService ClubService { get; set; } = default!;
|
||||||
[Inject] private AuthService AuthService { get; set; } = default!;
|
[Inject] private AuthService AuthService { get; set; } = default!;
|
||||||
[Inject] private UserContract.UserContractClient UserContract { get; set; } = default!;
|
[Inject] private UserContract.UserContractClient UserContract { get; set; } = default!;
|
||||||
|
[Inject] private PackageService PackageService { get; set; } = default!;
|
||||||
|
|
||||||
[Parameter] public EventCallback OnActivationSuccess { get; set; }
|
[Parameter] public EventCallback OnActivationSuccess { get; set; }
|
||||||
|
|
||||||
@@ -21,6 +22,18 @@ public partial class ActivationSection : ComponentBase
|
|||||||
private long _packageId = 1;
|
private long _packageId = 1;
|
||||||
private int _durationMonths = 1;
|
private int _durationMonths = 1;
|
||||||
private string? _activationCode;
|
private string? _activationCode;
|
||||||
|
private long _basePackagePrice;
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var packages = await PackageService.GetAllPackagesAsync();
|
||||||
|
var basePackage = packages.FirstOrDefault(p => p.IsBasePackage) ?? packages.FirstOrDefault();
|
||||||
|
_basePackagePrice = basePackage?.Price ?? 0;
|
||||||
|
}
|
||||||
|
catch { /* fallback to 0 */ }
|
||||||
|
}
|
||||||
|
|
||||||
private async Task HandleActivateAsync()
|
private async Task HandleActivateAsync()
|
||||||
{
|
{
|
||||||
@@ -75,9 +88,8 @@ public partial class ActivationSection : ComponentBase
|
|||||||
|
|
||||||
private string GetEstimatedCost()
|
private string GetEstimatedCost()
|
||||||
{
|
{
|
||||||
// فرمول تقریبی: 56M per month (base amount from BFF implementation)
|
if (_basePackagePrice <= 0) return "در حال بارگذاری...";
|
||||||
var baseAmount = 56_000_000;
|
var total = _basePackagePrice * _durationMonths;
|
||||||
var total = baseAmount * _durationMonths;
|
|
||||||
return $"{total:N0} تومان";
|
return $"{total:N0} تومان";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,15 +3,13 @@
|
|||||||
@using MudBlazor
|
@using MudBlazor
|
||||||
@inject ClubConfigurationService ClubConfigService
|
@inject ClubConfigurationService ClubConfigService
|
||||||
@inject IDialogService DialogService
|
@inject IDialogService DialogService
|
||||||
|
@inject IJSRuntime JS
|
||||||
|
|
||||||
<PageTitle>ویژگیهای باشگاه مشتریان</PageTitle>
|
<PageTitle>ویژگیهای باشگاه مشتریان</PageTitle>
|
||||||
|
|
||||||
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
||||||
<MudStack Spacing="3">
|
<MudStack Spacing="3">
|
||||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
<PageHeader Title="ویژگیهای باشگاه مشتریان" BackHref="@RouteConstants.Club.Membership" />
|
||||||
<MudText Typo="Typo.h5">ویژگیهای باشگاه مشتریان</MudText>
|
|
||||||
<MudButton Variant="Variant.Text" StartIcon="@Icons.Material.Filled.ArrowBack" Href="@RouteConstants.Club.Membership">بازگشت</MudButton>
|
|
||||||
</MudStack>
|
|
||||||
|
|
||||||
<!-- توضیحات کلی باشگاه -->
|
<!-- توضیحات کلی باشگاه -->
|
||||||
<MudPaper Elevation="2" Class="pa-4 rounded-lg">
|
<MudPaper Elevation="2" Class="pa-4 rounded-lg">
|
||||||
@@ -30,13 +28,13 @@
|
|||||||
|
|
||||||
@if (_isLoading)
|
@if (_isLoading)
|
||||||
{
|
{
|
||||||
<MudProgressLinear Color="Color.Primary" Indeterminate="true" />
|
<LoadingState Message="در حال دریافت ویژگیها..." />
|
||||||
}
|
}
|
||||||
else if (_features == null || !_features.Any())
|
else if (_features == null || !_features.Any())
|
||||||
{
|
{
|
||||||
<MudAlert Severity="Severity.Info">
|
<EmptyState Icon="@Icons.Material.Filled.Stars"
|
||||||
هنوز ویژگیای برای شما فعال نشده است.
|
Title="هنوز ویژگیای برای شما فعال نشده است"
|
||||||
</MudAlert>
|
Description="با خرید پکیج یا ارتقاء عضویت، ویژگیهای اختصاصی فعال میشوند." />
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -73,7 +71,7 @@
|
|||||||
FullWidth="true"
|
FullWidth="true"
|
||||||
Size="Size.Large"
|
Size="Size.Large"
|
||||||
StartIcon="@Icons.Material.Filled.ArrowBack"
|
StartIcon="@Icons.Material.Filled.ArrowBack"
|
||||||
Href="@RouteConstants.Club.Membership">
|
OnClick="GoBack">
|
||||||
بازگشت به صفحه باشگاه
|
بازگشت به صفحه باشگاه
|
||||||
</MudButton>
|
</MudButton>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
@@ -190,4 +188,6 @@
|
|||||||
_selectedFeature = feature;
|
_selectedFeature = feature;
|
||||||
_showDetailDialog = true;
|
_showDetailDialog = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task GoBack() => await JS.InvokeVoidAsync("history.back");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,14 +7,16 @@
|
|||||||
|
|
||||||
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
||||||
<MudStack Spacing="3">
|
<MudStack Spacing="3">
|
||||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
<PageHeader Title="عضویت باشگاه مشتریان" BackHref="@RouteConstants.Profile.Index" />
|
||||||
<MudText Typo="Typo.h5">عضویت باشگاه مشتریان</MudText>
|
|
||||||
<MudButton Variant="Variant.Text" StartIcon="@Icons.Material.Filled.ArrowBack" Href="@RouteConstants.Profile.Index">بازگشت</MudButton>
|
@* ── G6: راهنمای قرارداد باشگاه (Q28) ── *@
|
||||||
</MudStack>
|
<MudAlert Severity="Severity.Info" Variant="Variant.Text" Dense="true" Icon="@Icons.Material.Filled.Gavel">
|
||||||
|
قرارداد باشگاه مشتریان فقط یکبار امضا میشود و با هر بار خرید پکیج جدید نیازی به امضای مجدد نیست.
|
||||||
|
</MudAlert>
|
||||||
|
|
||||||
@if (_isLoading)
|
@if (_isLoading)
|
||||||
{
|
{
|
||||||
<MudProgressLinear Color="Color.Primary" Indeterminate="true" />
|
<LoadingState Message="در حال دریافت اطلاعات عضویت..." />
|
||||||
}
|
}
|
||||||
else if (_membership is not null)
|
else if (_membership is not null)
|
||||||
{
|
{
|
||||||
@@ -56,19 +58,6 @@
|
|||||||
<MudAlert Severity="Severity.Warning" Variant="Variant.Outlined">
|
<MudAlert Severity="Severity.Warning" Variant="Variant.Outlined">
|
||||||
<MudText>شما هنوز عضو باشگاه مشتریان نیستید. برای استفاده از مزایای ویژه، اکنون فعال کنید!</MudText>
|
<MudText>شما هنوز عضو باشگاه مشتریان نیستید. برای استفاده از مزایای ویژه، اکنون فعال کنید!</MudText>
|
||||||
</MudAlert>
|
</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>
|
</MudStack>
|
||||||
</MudPaper>
|
</MudPaper>
|
||||||
@@ -80,8 +69,8 @@
|
|||||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2" Class="pa-2">
|
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2" Class="pa-2">
|
||||||
<MudIcon Icon="@Icons.Material.Filled.AccountBalanceWallet" Color="Color.Success" Size="Size.Large" />
|
<MudIcon Icon="@Icons.Material.Filled.AccountBalanceWallet" Color="Color.Success" Size="Size.Large" />
|
||||||
<MudStack Spacing="0">
|
<MudStack Spacing="0">
|
||||||
<MudText Typo="Typo.subtitle1">شارژ کیف پول فروشگاه تخفیفی</MudText>
|
<MudText Typo="Typo.subtitle1">شارژ کیف پول فروشگاه اعتباری</MudText>
|
||||||
<MudText Typo="Typo.caption" Color="Color.Default">شارژ ۵۶ میلیون تومان کیف پول فروشگاه تخفیفی</MudText>
|
<MudText Typo="Typo.caption" Color="Color.Default">شارژ برابر ارزش پکیج فعال در کیف پول فروشگاه اعتباری</MudText>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
<MudDivider />
|
<MudDivider />
|
||||||
@@ -103,11 +92,6 @@
|
|||||||
</MudStack>
|
</MudStack>
|
||||||
</MudPaper>
|
</MudPaper>
|
||||||
|
|
||||||
<!-- فعالسازی یا تمدید عضویت -->
|
|
||||||
@if (!_membership.IsActive || (_membership.DaysRemaining.HasValue && _membership.DaysRemaining.Value <= 30))
|
|
||||||
{
|
|
||||||
<ActivationSection OnActivationSuccess="HandleActivationSuccess" />
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- دکمه مشاهده ویژگیهای بیشتر -->
|
<!-- دکمه مشاهده ویژگیهای بیشتر -->
|
||||||
<MudButton Variant="Variant.Outlined"
|
<MudButton Variant="Variant.Outlined"
|
||||||
|
|||||||
@@ -8,15 +8,17 @@
|
|||||||
|
|
||||||
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
||||||
<MudStack Spacing="3">
|
<MudStack Spacing="3">
|
||||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
<PageHeader Title="پاداشهای من" BackHref="@RouteConstants.Profile.Index" />
|
||||||
<MudText Typo="Typo.h5">پاداشهای من</MudText>
|
|
||||||
<MudButton Variant="Variant.Text" StartIcon="@Icons.Material.Filled.ArrowBack" Href="@RouteConstants.Profile.Index">بازگشت</MudButton>
|
@* ── G5: توضیح محاسبه پاداش (Q28) ── *@
|
||||||
</MudStack>
|
<MudAlert Severity="Severity.Info" Variant="Variant.Text" Dense="true" Icon="@Icons.Material.Filled.Info">
|
||||||
|
پاداش هفتگی بر اساس هر پکیج جداگانه محاسبه میشود. هر پکیج Pool پورسانت مستقل دارد و Carryover بر اساس پکیج زیرمجموعههاست.
|
||||||
|
</MudAlert>
|
||||||
|
|
||||||
<!-- فیلترها -->
|
<!-- فیلترها -->
|
||||||
<MudPaper Elevation="2" Class="pa-3 rounded-lg">
|
<MudPaper Elevation="2" Class="pa-3 rounded-lg">
|
||||||
<MudGrid Spacing="2">
|
<MudGrid Spacing="2">
|
||||||
<MudItem xs="12" sm="6" md="4">
|
<MudItem xs="12" sm="6" md="3">
|
||||||
<WeekSelector @ref="_weekSelector"
|
<WeekSelector @ref="_weekSelector"
|
||||||
@bind-Value="_selectedWeekDefinition"
|
@bind-Value="_selectedWeekDefinition"
|
||||||
Label="انتخاب هفته"
|
Label="انتخاب هفته"
|
||||||
@@ -26,7 +28,7 @@
|
|||||||
Clearable="true"
|
Clearable="true"
|
||||||
OnlyActive="false" />
|
OnlyActive="false" />
|
||||||
</MudItem>
|
</MudItem>
|
||||||
<MudItem xs="12" sm="6" md="4">
|
<MudItem xs="12" sm="6" md="3">
|
||||||
<MudSelect T="string" @bind-Value="_filterStatus" Label="وضعیت" Variant="Variant.Outlined">
|
<MudSelect T="string" @bind-Value="_filterStatus" Label="وضعیت" Variant="Variant.Outlined">
|
||||||
<MudSelectItem T="string" Value="@string.Empty">همه</MudSelectItem>
|
<MudSelectItem T="string" Value="@string.Empty">همه</MudSelectItem>
|
||||||
<MudSelectItem T="string" Value="@("Pending")">در انتظار</MudSelectItem>
|
<MudSelectItem T="string" Value="@("Pending")">در انتظار</MudSelectItem>
|
||||||
@@ -37,7 +39,16 @@
|
|||||||
<MudSelectItem T="string" Value="@("Cancelled")">لغو شده</MudSelectItem>
|
<MudSelectItem T="string" Value="@("Cancelled")">لغو شده</MudSelectItem>
|
||||||
</MudSelect>
|
</MudSelect>
|
||||||
</MudItem>
|
</MudItem>
|
||||||
<MudItem xs="12" sm="12" md="4">
|
<MudItem xs="12" sm="6" md="3">
|
||||||
|
<MudSelect T="long?" @bind-Value="_filterPackageId" Label="پکیج" Variant="Variant.Outlined" Clearable="true">
|
||||||
|
<MudSelectItem T="long?" Value="@((long?)null)">همه پکیجها</MudSelectItem>
|
||||||
|
@foreach (var pkg in _packages)
|
||||||
|
{
|
||||||
|
<MudSelectItem T="long?" Value="@((long?)pkg.Id)">@pkg.Title</MudSelectItem>
|
||||||
|
}
|
||||||
|
</MudSelect>
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="12" sm="6" md="3">
|
||||||
<MudStack Row="true" Spacing="2" Style="height: 100%;" AlignItems="AlignItems.End">
|
<MudStack Row="true" Spacing="2" Style="height: 100%;" AlignItems="AlignItems.End">
|
||||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="ApplyFiltersAsync" StartIcon="@Icons.Material.Filled.FilterList" FullWidth="true">
|
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="ApplyFiltersAsync" StartIcon="@Icons.Material.Filled.FilterList" FullWidth="true">
|
||||||
اعمال فیلتر
|
اعمال فیلتر
|
||||||
@@ -52,7 +63,7 @@
|
|||||||
|
|
||||||
@if (_isLoading)
|
@if (_isLoading)
|
||||||
{
|
{
|
||||||
<MudProgressLinear Color="Color.Primary" Indeterminate="true" />
|
<LoadingState Message="در حال دریافت پاداشها..." />
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -90,6 +101,7 @@
|
|||||||
<MudTable Items="_payouts" Hover="true" Striped="true" Dense="true" FixedHeader="true" Height="400px">
|
<MudTable Items="_payouts" Hover="true" Striped="true" Dense="true" FixedHeader="true" Height="400px">
|
||||||
<HeaderContent>
|
<HeaderContent>
|
||||||
<MudTh>هفته</MudTh>
|
<MudTh>هفته</MudTh>
|
||||||
|
<MudTh>پکیج</MudTh>
|
||||||
<MudTh>امتیاز</MudTh>
|
<MudTh>امتیاز</MudTh>
|
||||||
<MudTh>مبلغ (تومان)</MudTh>
|
<MudTh>مبلغ (تومان)</MudTh>
|
||||||
<MudTh>وضعیت</MudTh>
|
<MudTh>وضعیت</MudTh>
|
||||||
@@ -100,6 +112,11 @@
|
|||||||
<MudTd DataLabel="هفته">
|
<MudTd DataLabel="هفته">
|
||||||
<MudChip T="string" Color="Color.Primary" Size="Size.Small" Variant="Variant.Outlined">@context.WeekDisplayName</MudChip>
|
<MudChip T="string" Color="Color.Primary" Size="Size.Small" Variant="Variant.Outlined">@context.WeekDisplayName</MudChip>
|
||||||
</MudTd>
|
</MudTd>
|
||||||
|
<MudTd DataLabel="پکیج">
|
||||||
|
<MudChip T="string" Color="Color.Tertiary" Size="Size.Small">
|
||||||
|
@(string.IsNullOrEmpty(context.PackageTitle) ? "-" : context.PackageTitle)
|
||||||
|
</MudChip>
|
||||||
|
</MudTd>
|
||||||
<MudTd DataLabel="امتیاز">@context.BalancesEarned</MudTd>
|
<MudTd DataLabel="امتیاز">@context.BalancesEarned</MudTd>
|
||||||
<MudTd DataLabel="مبلغ">
|
<MudTd DataLabel="مبلغ">
|
||||||
<MudText Color="Color.Success"><strong>@context.AmountFormatted</strong></MudText>
|
<MudText Color="Color.Success"><strong>@context.AmountFormatted</strong></MudText>
|
||||||
@@ -130,6 +147,10 @@
|
|||||||
<MudChip T="string" Color="Color.Primary" Size="Size.Small" Variant="Variant.Outlined">@payout.WeekDisplayName</MudChip>
|
<MudChip T="string" Color="Color.Primary" Size="Size.Small" Variant="Variant.Outlined">@payout.WeekDisplayName</MudChip>
|
||||||
<MudChip T="string" Color="@GetStatusColor(payout.StatusColor)" Size="Size.Small">@payout.StatusText</MudChip>
|
<MudChip T="string" Color="@GetStatusColor(payout.StatusColor)" Size="Size.Small">@payout.StatusText</MudChip>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
|
@if (!string.IsNullOrEmpty(payout.PackageTitle))
|
||||||
|
{
|
||||||
|
<MudChip T="string" Color="Color.Tertiary" Size="Size.Small">@payout.PackageTitle</MudChip>
|
||||||
|
}
|
||||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
||||||
<MudText Typo="Typo.h6" Color="Color.Success">@payout.AmountFormatted</MudText>
|
<MudText Typo="Typo.h6" Color="Color.Success">@payout.AmountFormatted</MudText>
|
||||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">امتیاز: @payout.BalancesEarned</MudText>
|
<MudText Typo="Typo.caption" Class="mud-text-secondary">امتیاز: @payout.BalancesEarned</MudText>
|
||||||
|
|||||||
@@ -8,8 +8,10 @@ namespace FrontOffice.Main.Pages.Commission;
|
|||||||
public partial class CommissionDashboardPage : ComponentBase
|
public partial class CommissionDashboardPage : ComponentBase
|
||||||
{
|
{
|
||||||
[Inject] private CommissionService CommissionService { get; set; } = default!;
|
[Inject] private CommissionService CommissionService { get; set; } = default!;
|
||||||
|
[Inject] private PackageService PackageService { get; set; } = default!;
|
||||||
|
|
||||||
private List<CommissionPayoutDto> _payouts = new();
|
private List<CommissionPayoutDto> _payouts = new();
|
||||||
|
private List<PackageDto> _packages = new();
|
||||||
private int _totalCount;
|
private int _totalCount;
|
||||||
private long _totalAmount;
|
private long _totalAmount;
|
||||||
private int _pageNumber = 1;
|
private int _pageNumber = 1;
|
||||||
@@ -19,12 +21,26 @@ public partial class CommissionDashboardPage : ComponentBase
|
|||||||
private WeekSelector? _weekSelector;
|
private WeekSelector? _weekSelector;
|
||||||
private WeekDefinitionDto? _selectedWeekDefinition;
|
private WeekDefinitionDto? _selectedWeekDefinition;
|
||||||
private string _filterStatus = string.Empty;
|
private string _filterStatus = string.Empty;
|
||||||
|
private long? _filterPackageId;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
|
await LoadPackagesAsync();
|
||||||
await LoadPayoutsAsync();
|
await LoadPayoutsAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task LoadPackagesAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_packages = await PackageService.GetAllPackagesAsync();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
_packages = new List<PackageDto>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task LoadPayoutsAsync()
|
private async Task LoadPayoutsAsync()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -35,7 +51,8 @@ public partial class CommissionDashboardPage : ComponentBase
|
|||||||
weekDefinitionId,
|
weekDefinitionId,
|
||||||
_filterStatus,
|
_filterStatus,
|
||||||
_pageNumber,
|
_pageNumber,
|
||||||
_pageSize
|
_pageSize,
|
||||||
|
_filterPackageId
|
||||||
);
|
);
|
||||||
_payouts = result.Payouts;
|
_payouts = result.Payouts;
|
||||||
_totalCount = result.TotalCount;
|
_totalCount = result.TotalCount;
|
||||||
@@ -61,6 +78,7 @@ public partial class CommissionDashboardPage : ComponentBase
|
|||||||
{
|
{
|
||||||
_selectedWeekDefinition = null;
|
_selectedWeekDefinition = null;
|
||||||
_filterStatus = string.Empty;
|
_filterStatus = string.Empty;
|
||||||
|
_filterPackageId = null;
|
||||||
_pageNumber = 1;
|
_pageNumber = 1;
|
||||||
await LoadPayoutsAsync();
|
await LoadPayoutsAsync();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,15 +7,12 @@
|
|||||||
|
|
||||||
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
||||||
<MudStack Spacing="3">
|
<MudStack Spacing="3">
|
||||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
<PageHeader Title="گزارش هفتگی پاداش" BackHref="@RouteConstants.Commission.Dashboard" />
|
||||||
<MudText Typo="Typo.h5">گزارش هفتگی پاداش</MudText>
|
|
||||||
<MudButton Variant="Variant.Text" StartIcon="@Icons.Material.Filled.ArrowBack" Href="@RouteConstants.Commission.Dashboard">بازگشت</MudButton>
|
|
||||||
</MudStack>
|
|
||||||
|
|
||||||
<!-- انتخاب هفته -->
|
<!-- انتخاب هفته -->
|
||||||
<MudPaper Elevation="2" Class="pa-3 rounded-lg">
|
<MudPaper Elevation="2" Class="pa-3 rounded-lg">
|
||||||
<MudGrid Spacing="2">
|
<MudGrid Spacing="2">
|
||||||
<MudItem xs="8" sm="6" md="6">
|
<MudItem xs="8" sm="4" md="4">
|
||||||
<WeekSelector @ref="_weekSelector"
|
<WeekSelector @ref="_weekSelector"
|
||||||
@bind-Value="_selectedWeekDefinition"
|
@bind-Value="_selectedWeekDefinition"
|
||||||
Label="انتخاب هفته"
|
Label="انتخاب هفته"
|
||||||
@@ -24,15 +21,15 @@
|
|||||||
Dense="false"
|
Dense="false"
|
||||||
OnlyActive="true" />
|
OnlyActive="true" />
|
||||||
</MudItem>
|
</MudItem>
|
||||||
@* <MudItem xs="4" sm="3" md="3"> *@
|
<MudItem xs="4" sm="4" md="4">
|
||||||
@* <MudButton Variant="Variant.Outlined" *@
|
<MudSelect T="long?" @bind-Value="_filterPackageId" Label="پکیج" Variant="Variant.Outlined" Clearable="true">
|
||||||
@* OnClick="LoadCurrentWeekAsync" *@
|
<MudSelectItem T="long?" Value="@((long?)null)">همه پکیجها</MudSelectItem>
|
||||||
@* StartIcon="@Icons.Material.Filled.Today" *@
|
@foreach (var pkg in _packages)
|
||||||
@* FullWidth="true" *@
|
{
|
||||||
@* Style="height: 56px;"> *@
|
<MudSelectItem T="long?" Value="@((long?)pkg.Id)">@pkg.Title</MudSelectItem>
|
||||||
@* هفته جاری *@
|
}
|
||||||
@* </MudButton> *@
|
</MudSelect>
|
||||||
@* </MudItem> *@
|
</MudItem>
|
||||||
<MudItem xs="4" sm="3" md="3" Class="pt-6">
|
<MudItem xs="4" sm="3" md="3" Class="pt-6">
|
||||||
<MudButton Variant="Variant.Filled"
|
<MudButton Variant="Variant.Filled"
|
||||||
Color="Color.Primary"
|
Color="Color.Primary"
|
||||||
@@ -50,7 +47,7 @@
|
|||||||
|
|
||||||
@if (_isLoading)
|
@if (_isLoading)
|
||||||
{
|
{
|
||||||
<MudProgressLinear Color="Color.Primary" Indeterminate="true" />
|
<LoadingState Message="در حال دریافت گزارش..." />
|
||||||
}
|
}
|
||||||
else if (_weeklyBalance is not null)
|
else if (_weeklyBalance is not null)
|
||||||
{
|
{
|
||||||
@@ -69,6 +66,12 @@
|
|||||||
<MudItem xs="6" sm="4">
|
<MudItem xs="6" sm="4">
|
||||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">پایان: <strong>@_weeklyBalance.EndDatePersian</strong></MudText>
|
<MudText Typo="Typo.caption" Class="mud-text-secondary">پایان: <strong>@_weeklyBalance.EndDatePersian</strong></MudText>
|
||||||
</MudItem>
|
</MudItem>
|
||||||
|
@if (!string.IsNullOrEmpty(_weeklyBalance.PackageTitle))
|
||||||
|
{
|
||||||
|
<MudItem xs="12" sm="12">
|
||||||
|
<MudChip T="string" Color="Color.Info" Size="Size.Small" Icon="@Icons.Material.Filled.Inventory2">@_weeklyBalance.PackageTitle</MudChip>
|
||||||
|
</MudItem>
|
||||||
|
}
|
||||||
</MudGrid>
|
</MudGrid>
|
||||||
</MudPaper>
|
</MudPaper>
|
||||||
|
|
||||||
@@ -76,11 +79,11 @@
|
|||||||
<MudGrid Spacing="2">
|
<MudGrid Spacing="2">
|
||||||
|
|
||||||
<MudItem xs="6">
|
<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">
|
<MudStack Spacing="1" AlignItems="AlignItems.Center">
|
||||||
<MudIcon Icon="@Icons.Material.Filled.ChevronRight" Color="Color.Success" Size="Size.Large" />
|
<MudIcon Icon="@Icons.Material.Filled.ChevronRight" Color="Color.Success" Size="Size.Large" />
|
||||||
<MudText Typo="Typo.subtitle2" Color="Color.Success">تیم دوم</MudText>
|
<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"
|
<MudProgressLinear Color="Color.Success"
|
||||||
Value="@GetRightPercentage()"
|
Value="@GetRightPercentage()"
|
||||||
Size="Size.Medium"
|
Size="Size.Medium"
|
||||||
@@ -93,11 +96,11 @@
|
|||||||
</MudPaper>
|
</MudPaper>
|
||||||
</MudItem>
|
</MudItem>
|
||||||
<MudItem xs="6">
|
<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">
|
<MudStack Spacing="1" AlignItems="AlignItems.Center">
|
||||||
<MudIcon Icon="@Icons.Material.Filled.ChevronLeft" Color="Color.Info" Size="Size.Large"/>
|
<MudIcon Icon="@Icons.Material.Filled.ChevronLeft" Color="Color.Info" Size="Size.Large"/>
|
||||||
<MudText Typo="Typo.subtitle2" Color="Color.Info">تیم اول</MudText>
|
<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"
|
<MudProgressLinear Color="Color.Info"
|
||||||
Value="@GetLeftPercentage()"
|
Value="@GetLeftPercentage()"
|
||||||
Size="Size.Medium"
|
Size="Size.Medium"
|
||||||
@@ -114,7 +117,7 @@
|
|||||||
|
|
||||||
<!-- محاسبات پاداش - فشردهتر -->
|
<!-- محاسبات پاداش - فشردهتر -->
|
||||||
<MudPaper Elevation="2" Class="pa-4 rounded-lg">
|
<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;">
|
<MudSimpleTable Dense="true" Hover="true" Style="background: transparent;">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ namespace FrontOffice.Main.Pages.Commission;
|
|||||||
public partial class WeeklyBalancePage : ComponentBase
|
public partial class WeeklyBalancePage : ComponentBase
|
||||||
{
|
{
|
||||||
[Inject] private CommissionService CommissionService { get; set; } = default!;
|
[Inject] private CommissionService CommissionService { get; set; } = default!;
|
||||||
|
[Inject] private PackageService PackageService { get; set; } = default!;
|
||||||
[Inject] private ISnackbar SnackbarService { get; set; } = default!;
|
[Inject] private ISnackbar SnackbarService { get; set; } = default!;
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
@@ -17,6 +18,8 @@ public partial class WeeklyBalancePage : ComponentBase
|
|||||||
private WeekSelector? _weekSelector;
|
private WeekSelector? _weekSelector;
|
||||||
private WeekDefinitionDto? _selectedWeekDefinition;
|
private WeekDefinitionDto? _selectedWeekDefinition;
|
||||||
private WeeklyBalanceDto? _weeklyBalance;
|
private WeeklyBalanceDto? _weeklyBalance;
|
||||||
|
private List<PackageDto> _packages = new();
|
||||||
|
private long? _filterPackageId;
|
||||||
private bool _isLoading = true;
|
private bool _isLoading = true;
|
||||||
private bool _hasError = false;
|
private bool _hasError = false;
|
||||||
|
|
||||||
@@ -34,6 +37,8 @@ public partial class WeeklyBalancePage : ComponentBase
|
|||||||
{
|
{
|
||||||
if (firstRender)
|
if (firstRender)
|
||||||
{
|
{
|
||||||
|
await LoadPackagesAsync();
|
||||||
|
|
||||||
// Wait for WeekSelector to initialize and load its cached weeks
|
// Wait for WeekSelector to initialize and load its cached weeks
|
||||||
if (_weekSelector != null)
|
if (_weekSelector != null)
|
||||||
{
|
{
|
||||||
@@ -88,7 +93,7 @@ public partial class WeeklyBalancePage : ComponentBase
|
|||||||
|
|
||||||
long? weekDefinitionId = _selectedWeekDefinition?.Id;
|
long? weekDefinitionId = _selectedWeekDefinition?.Id;
|
||||||
|
|
||||||
_weeklyBalance = await CommissionService.GetMyWeeklyBalanceAsync(weekDefinitionId);
|
_weeklyBalance = await CommissionService.GetMyWeeklyBalanceAsync(weekDefinitionId, _filterPackageId);
|
||||||
|
|
||||||
// Update week display name from selected definition if available
|
// Update week display name from selected definition if available
|
||||||
if (_selectedWeekDefinition != null && _weeklyBalance != null)
|
if (_selectedWeekDefinition != null && _weeklyBalance != null)
|
||||||
@@ -113,6 +118,18 @@ public partial class WeeklyBalancePage : ComponentBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task LoadPackagesAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_packages = await PackageService.GetAllPackagesAsync();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
_packages = new();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task LoadCurrentWeekAsync()
|
private async Task LoadCurrentWeekAsync()
|
||||||
{
|
{
|
||||||
if (_weekSelector != null)
|
if (_weekSelector != null)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
@attribute [Route(RouteConstants.Contact.Index)]
|
@attribute [Route(RouteConstants.Contact.Index)]
|
||||||
|
@inject SitePageSettingsService PageSettingsService
|
||||||
|
|
||||||
<PageTitle>ارتباط با ما | کارا بازار سلامت</PageTitle>
|
<PageTitle>ارتباط با ما | کارا بازار سلامت</PageTitle>
|
||||||
|
|
||||||
@@ -8,10 +9,10 @@
|
|||||||
<MudStack AlignItems="AlignItems.Center" Spacing="4">
|
<MudStack AlignItems="AlignItems.Center" Spacing="4">
|
||||||
<MudChip T="string" Color="Color.Secondary" Variant="Variant.Filled" Class="mb-2">ارتباط با ما</MudChip>
|
<MudChip T="string" Color="Color.Secondary" Variant="Variant.Filled" Class="mb-2">ارتباط با ما</MudChip>
|
||||||
<MudText Typo="Typo.h2" Align="Align.Center" Class="mb-3">
|
<MudText Typo="Typo.h2" Align="Align.Center" Class="mb-3">
|
||||||
آماده شنیدن صدای شما هستیم
|
@(_page?.HeroTitle ?? "آماده شنیدن صدای شما هستیم")
|
||||||
</MudText>
|
</MudText>
|
||||||
<MudText Typo="Typo.body1" Align="Align.Center" Class="mud-text-secondary mb-6" Style="max-width:600px">
|
<MudText Typo="Typo.body1" Align="Align.Center" Class="mud-text-secondary mb-6" Style="max-width:600px">
|
||||||
سوالات، پیشنهادات یا انتقادات خود را با ما در میان بگذارید. تیم ما آماده پاسخگویی به شماست.
|
@(_page?.HeroSubtitle ?? "سوالات، پیشنهادات یا انتقادات خود را با ما در میان بگذارید. تیم ما آماده پاسخگویی به شماست.")
|
||||||
</MudText>
|
</MudText>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
</MudContainer>
|
</MudContainer>
|
||||||
@@ -25,7 +26,7 @@
|
|||||||
<MudGrid Spacing="6">
|
<MudGrid Spacing="6">
|
||||||
<!-- Contact Form -->
|
<!-- Contact Form -->
|
||||||
<MudItem xs="12" lg="8">
|
<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>
|
<MudText Typo="Typo.h5" Class="mb-6">فرم تماس</MudText>
|
||||||
|
|
||||||
<MudForm @ref="_form" Model="_contactForm">
|
<MudForm @ref="_form" Model="_contactForm">
|
||||||
@@ -72,12 +73,12 @@
|
|||||||
Variant="Variant.Outlined"
|
Variant="Variant.Outlined"
|
||||||
Required="true"
|
Required="true"
|
||||||
RequiredError="انتخاب موضوع الزامی است.">
|
RequiredError="انتخاب موضوع الزامی است.">
|
||||||
<MudSelectItem Value="@("general")">عمومی</MudSelectItem>
|
<MudSelectItem T="string" Value="@("general")">عمومی</MudSelectItem>
|
||||||
<MudSelectItem Value="@("support")">پشتیبانی فنی</MudSelectItem>
|
<MudSelectItem T="string" Value="@("support")">پشتیبانی فنی</MudSelectItem>
|
||||||
<MudSelectItem Value="@("sales")">فروش و قیمتگذاری</MudSelectItem>
|
<MudSelectItem T="string" Value="@("sales")">فروش و قیمتگذاری</MudSelectItem>
|
||||||
<MudSelectItem Value="@("partnership")">شریک تجاری</MudSelectItem>
|
<MudSelectItem T="string" Value="@("partnership")">شریک تجاری</MudSelectItem>
|
||||||
<MudSelectItem Value="@("complaint")">شکایت</MudSelectItem>
|
<MudSelectItem T="string" Value="@("complaint")">شکایت</MudSelectItem>
|
||||||
<MudSelectItem Value="@("suggestion")">پیشنهاد</MudSelectItem>
|
<MudSelectItem T="string" Value="@("suggestion")">پیشنهاد</MudSelectItem>
|
||||||
</MudSelect>
|
</MudSelect>
|
||||||
</MudItem>
|
</MudItem>
|
||||||
|
|
||||||
@@ -123,7 +124,7 @@
|
|||||||
<MudItem xs="12" lg="4">
|
<MudItem xs="12" lg="4">
|
||||||
<MudStack Spacing="4">
|
<MudStack Spacing="4">
|
||||||
<!-- Contact Details -->
|
<!-- Contact Details -->
|
||||||
<MudPaper Elevation="3" Class="pa-6">
|
<MudPaper Elevation="2" Class="pa-6">
|
||||||
<MudText Typo="Typo.h6" Class="mb-4">اطلاعات تماس</MudText>
|
<MudText Typo="Typo.h6" Class="mb-4">اطلاعات تماس</MudText>
|
||||||
|
|
||||||
<MudStack Spacing="4">
|
<MudStack Spacing="4">
|
||||||
@@ -132,7 +133,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<MudText Typo="Typo.body2" >آدرس</MudText>
|
<MudText Typo="Typo.body2" >آدرس</MudText>
|
||||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">
|
<MudText Typo="Typo.caption" Class="mud-text-secondary">
|
||||||
کرج مهرویلا میدان مادر ساختمان بزرگمهر طبقه ۴ واحد ۱۶
|
@(_settings?.Address ?? "کرج مهرویلا میدان مادر ساختمان بزرگمهر طبقه ۴ واحد ۱۶")
|
||||||
</MudText>
|
</MudText>
|
||||||
</div>
|
</div>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
@@ -141,7 +142,7 @@
|
|||||||
<MudIcon Icon="@Icons.Material.Filled.Phone" Color="Color.Success" Size="Size.Large" />
|
<MudIcon Icon="@Icons.Material.Filled.Phone" Color="Color.Success" Size="Size.Large" />
|
||||||
<div>
|
<div>
|
||||||
<MudText Typo="Typo.body2" >تلفن</MudText>
|
<MudText Typo="Typo.body2" >تلفن</MudText>
|
||||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">026-34233563</MudText>
|
<MudText Typo="Typo.caption" Class="mud-text-secondary">@(_settings?.Phone ?? "026-34233563")</MudText>
|
||||||
</div>
|
</div>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
|
|
||||||
@@ -149,7 +150,7 @@
|
|||||||
<MudIcon Icon="@Icons.Material.Filled.Email" Color="Color.Info" Size="Size.Large" />
|
<MudIcon Icon="@Icons.Material.Filled.Email" Color="Color.Info" Size="Size.Large" />
|
||||||
<div>
|
<div>
|
||||||
<MudText Typo="Typo.body2" >ایمیل</MudText>
|
<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">@(_settings?.Email ?? "info@kbs1.co")</MudText>
|
||||||
</div>
|
</div>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
|
|
||||||
@@ -158,8 +159,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<MudText Typo="Typo.body2" >ساعات کاری</MudText>
|
<MudText Typo="Typo.body2" >ساعات کاری</MudText>
|
||||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">
|
<MudText Typo="Typo.caption" Class="mud-text-secondary">
|
||||||
شنبه تا پنجشنبه<br />
|
@((MarkupString)(_settings?.WorkingHours ?? "شنبه تا پنجشنبه<br />۹ صبح تا ۶ عصر"))
|
||||||
۹ صبح تا ۶ عصر
|
|
||||||
</MudText>
|
</MudText>
|
||||||
</div>
|
</div>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
@@ -167,14 +167,14 @@
|
|||||||
</MudPaper>
|
</MudPaper>
|
||||||
|
|
||||||
<!-- Social Media -->
|
<!-- Social Media -->
|
||||||
<MudPaper Elevation="3" Class="pa-6">
|
<MudPaper Elevation="2" Class="pa-6">
|
||||||
<MudText Typo="Typo.h6" Class="mb-4">شبکههای اجتماعی</MudText>
|
<MudText Typo="Typo.h6" Class="mb-4">شبکههای اجتماعی</MudText>
|
||||||
|
|
||||||
<MudStack Spacing="3">
|
<MudStack Spacing="3">
|
||||||
<MudButton Variant="Variant.Outlined"
|
<MudButton Variant="Variant.Outlined"
|
||||||
Color="Color.Inherit"
|
Color="Color.Inherit"
|
||||||
StartIcon="@Icons.Custom.Brands.Telegram"
|
StartIcon="@Icons.Custom.Brands.Telegram"
|
||||||
Href="https://t.me/kbs1"
|
Href="@(_settings?.TelegramUrl ?? "https://t.me/kbs1")"
|
||||||
Target="_blank"
|
Target="_blank"
|
||||||
FullWidth="true">
|
FullWidth="true">
|
||||||
تلگرام
|
تلگرام
|
||||||
@@ -183,7 +183,7 @@
|
|||||||
<MudButton Variant="Variant.Outlined"
|
<MudButton Variant="Variant.Outlined"
|
||||||
Color="Color.Inherit"
|
Color="Color.Inherit"
|
||||||
StartIcon="@Icons.Custom.Brands.Instagram"
|
StartIcon="@Icons.Custom.Brands.Instagram"
|
||||||
Href="https://instagram.com/kbs1"
|
Href="@(_settings?.InstagramUrl ?? "https://instagram.com/kbs1")"
|
||||||
Target="_blank"
|
Target="_blank"
|
||||||
FullWidth="true">
|
FullWidth="true">
|
||||||
اینستاگرام
|
اینستاگرام
|
||||||
@@ -192,7 +192,7 @@
|
|||||||
<MudButton Variant="Variant.Outlined"
|
<MudButton Variant="Variant.Outlined"
|
||||||
Color="Color.Inherit"
|
Color="Color.Inherit"
|
||||||
StartIcon="@Icons.Custom.Brands.LinkedIn"
|
StartIcon="@Icons.Custom.Brands.LinkedIn"
|
||||||
Href="https://linkedin.com/company/kbs1"
|
Href="@(_settings?.LinkedinUrl ?? "https://linkedin.com/company/kbs1")"
|
||||||
Target="_blank"
|
Target="_blank"
|
||||||
FullWidth="true">
|
FullWidth="true">
|
||||||
لینکدین
|
لینکدین
|
||||||
@@ -201,7 +201,7 @@
|
|||||||
<MudButton Variant="Variant.Outlined"
|
<MudButton Variant="Variant.Outlined"
|
||||||
Color="Color.Inherit"
|
Color="Color.Inherit"
|
||||||
StartIcon="@Icons.Custom.Brands.WhatsApp"
|
StartIcon="@Icons.Custom.Brands.WhatsApp"
|
||||||
Href="https://wa.me/989123456789"
|
Href="@(_settings?.WhatsappUrl ?? "https://wa.me/989123456789")"
|
||||||
Target="_blank"
|
Target="_blank"
|
||||||
FullWidth="true">
|
FullWidth="true">
|
||||||
واتساپ
|
واتساپ
|
||||||
@@ -219,14 +219,14 @@
|
|||||||
<MudContainer MaxWidth="MaxWidth.Large">
|
<MudContainer MaxWidth="MaxWidth.Large">
|
||||||
<MudText Typo="Typo.h4" Align="Align.Center" Class="mb-8">موقعیت مکانی</MudText>
|
<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 -->
|
<!-- 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">
|
<MudStack AlignItems="AlignItems.Center" Spacing="3">
|
||||||
<MudIcon Icon="@Icons.Material.Filled.Map" Size="Size.Large" Style="color: white;" />
|
<MudIcon Icon="@Icons.Material.Filled.Map" Size="Size.Large" Style="color: white;" />
|
||||||
<MudText Typo="Typo.h6" Style="color: white;">نقشه موقعیت مکانی</MudText>
|
<MudText Typo="Typo.h6" Style="color: white;">نقشه موقعیت مکانی</MudText>
|
||||||
<MudText Typo="Typo.body2" Style="color: rgba(255,255,255,0.8);" Align="Align.Center">
|
<MudText Typo="Typo.body2" Style="color: rgba(255,255,255,0.8);" Align="Align.Center">
|
||||||
کرج مهرویلا میدان مادر ساختمان بزرگمهر طبقه ۴ واحد ۱۶
|
@(_settings?.Address ?? "کرج مهرویلا میدان مادر ساختمان بزرگمهر طبقه ۴ واحد ۱۶")
|
||||||
</MudText>
|
</MudText>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
|
using FrontOffice.Main.Utilities;
|
||||||
using MudBlazor;
|
using MudBlazor;
|
||||||
using Severity = MudBlazor.Severity;
|
using Severity = MudBlazor.Severity;
|
||||||
|
|
||||||
namespace FrontOffice.Main.Pages;
|
namespace FrontOffice.Main.Pages;
|
||||||
|
|
||||||
public partial class Contact
|
public partial class Contact
|
||||||
{
|
{
|
||||||
private ContactForm _contactForm = new();
|
private ContactForm _contactForm = new();
|
||||||
@@ -10,6 +12,27 @@ public partial class Contact
|
|||||||
private bool _isSubmitting;
|
private bool _isSubmitting;
|
||||||
private readonly ContactFormValidator _contactFormValidator = new();
|
private readonly ContactFormValidator _contactFormValidator = new();
|
||||||
|
|
||||||
|
// Dynamic CMS data (new simplified system)
|
||||||
|
private PageSettingsDto? _page;
|
||||||
|
private ContactSettings? _settings;
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_page = await PageSettingsService.GetPageAsync("contact");
|
||||||
|
|
||||||
|
if (_page != null)
|
||||||
|
{
|
||||||
|
_settings = _page.GetSettings<ContactSettings>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Fallback: all values remain null → hardcoded defaults will render
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task SubmitContactForm()
|
private async Task SubmitContactForm()
|
||||||
{
|
{
|
||||||
if (_form is null) return;
|
if (_form is null) return;
|
||||||
@@ -55,14 +78,12 @@ public partial class Contact
|
|||||||
|
|
||||||
private void CallSupport()
|
private void CallSupport()
|
||||||
{
|
{
|
||||||
// TODO: Initiate phone call or show phone number
|
Snackbar.Add($"شماره تماس: {_settings?.Phone ?? "026-34233563"}", Severity.Info);
|
||||||
Snackbar.Add("شماره تماس: ۰۲۱-۱۲۳۴۵۶۷۸", Severity.Info);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SendEmail()
|
private void SendEmail()
|
||||||
{
|
{
|
||||||
// TODO: Open email client or redirect to email page
|
Snackbar.Add($"ایمیل: {_settings?.Email ?? "info@kbs1.co"}", Severity.Info);
|
||||||
Snackbar.Add("ایمیل: info@kbs1.co", Severity.Info);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ContactForm
|
public class ContactForm
|
||||||
@@ -87,4 +108,17 @@ public partial class Contact
|
|||||||
RuleFor(x => x.Message).NotEmpty().MinimumLength(10).WithMessage("پیام باید حداقل ۱۰ کاراکتر باشد");
|
RuleFor(x => x.Message).NotEmpty().MinimumLength(10).WithMessage("پیام باید حداقل ۱۰ کاراکتر باشد");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DTO class for SettingsJson deserialization
|
||||||
|
private class ContactSettings
|
||||||
|
{
|
||||||
|
public string? Address { get; set; }
|
||||||
|
public string? Phone { get; set; }
|
||||||
|
public string? Email { get; set; }
|
||||||
|
public string? WorkingHours { get; set; }
|
||||||
|
public string? TelegramUrl { get; set; }
|
||||||
|
public string? InstagramUrl { get; set; }
|
||||||
|
public string? LinkedinUrl { get; set; }
|
||||||
|
public string? WhatsappUrl { get; set; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,204 @@
|
|||||||
|
@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="rounded-lg" ObjectFit="ObjectFit.Cover" />
|
||||||
|
<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-lg" ObjectFit="ObjectFit.Cover" />
|
||||||
|
<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>
|
||||||
|
}
|
||||||
|
@if (VAT.IsEnabled)
|
||||||
|
{
|
||||||
|
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
||||||
|
<MudText Typo="Typo.caption" Class="mud-text-secondary">مالیات ارزش افزوده (@VAT.VatPercentage%):</MudText>
|
||||||
|
<MudText Typo="Typo.caption" Class="mud-text-secondary">@FormatPrice(VatAmount)+ تومان</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(TotalWithVat) تومان</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>
|
||||||
|
}
|
||||||
|
@if (VAT.IsEnabled)
|
||||||
|
{
|
||||||
|
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||||||
|
<MudText Typo="Typo.body2" Class="mud-text-secondary">مالیات بر ارزش افزوده (@VAT.VatPercentage%):</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(TotalWithVat) تومان</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,75 @@
|
|||||||
|
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!;
|
||||||
|
[Inject] private VATService VAT { get; set; } = default!;
|
||||||
|
[Inject] private AuthDialogService AuthDialogService { get; set; } = default!;
|
||||||
|
[Inject] private AuthService AuthService { get; set; } = default!;
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
if (!await AuthService.IsAuthenticatedAsync())
|
||||||
|
{
|
||||||
|
await AuthDialogService.ShowAuthDialogAsync();
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
|
{
|
||||||
|
if (firstRender)
|
||||||
|
{
|
||||||
|
await VAT.LoadAsync();
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private long VatAmount => VAT.CalculateVAT(DiscountCart.FinalPrice);
|
||||||
|
private long TotalWithVat => DiscountCart.FinalPrice + VatAmount;
|
||||||
|
|
||||||
|
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-lg" ObjectFit="ObjectFit.Cover" />
|
||||||
|
<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">مالیات بر ارزش افزوده (@VAT.VatPercentage٪):</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,129 @@
|
|||||||
|
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!;
|
||||||
|
[Inject] private VATService VAT { get; set; } = default!;
|
||||||
|
[Inject] private AuthDialogService AuthDialogService { get; set; } = default!;
|
||||||
|
[Inject] private AuthService AuthService { get; set; } = default!;
|
||||||
|
|
||||||
|
private List<CustomerAddressModel> _addresses = new();
|
||||||
|
private CustomerAddressModel? _selectedAddress;
|
||||||
|
private bool _loadingAddresses;
|
||||||
|
|
||||||
|
private string? _notes;
|
||||||
|
private bool _placing;
|
||||||
|
|
||||||
|
/// <summary>مبلغ درگاه قبل از مالیات (جمع کل - اعتبار)</summary>
|
||||||
|
private long NetGatewayAmount => DiscountCart.TotalPrice - DiscountCart.TotalDiscount;
|
||||||
|
|
||||||
|
/// <summary>مالیات بر ارزش افزوده</summary>
|
||||||
|
private long VatAmount => VAT.CalculateVAT(NetGatewayAmount);
|
||||||
|
|
||||||
|
/// <summary>مبلغ نهایی قابل پرداخت (شامل VAT)</summary>
|
||||||
|
private long FinalGatewayAmount => NetGatewayAmount + VatAmount;
|
||||||
|
|
||||||
|
private bool CanPlaceOrder => DiscountCart.Items.Count > 0 && _selectedAddress is not null;
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
if (!await AuthService.IsAuthenticatedAsync())
|
||||||
|
{
|
||||||
|
await AuthDialogService.ShowAuthDialogAsync();
|
||||||
|
}
|
||||||
|
await VAT.LoadAsync();
|
||||||
|
await DiscountCart.EnsureInitializedAsync();
|
||||||
|
var userInfo = await AuthService.GetUserAuthInfo();
|
||||||
|
if (userInfo.HasAddress)
|
||||||
|
await LoadAddresses();
|
||||||
|
else
|
||||||
|
_addresses = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
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,217 @@
|
|||||||
|
@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>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (VAT.IsEnabled)
|
||||||
|
{
|
||||||
|
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||||||
|
<MudText Typo="Typo.body2" Class="mud-text-secondary">مالیات بر ارزش افزوده (@VAT.VatPercentage%):</MudText>
|
||||||
|
<MudText Typo="Typo.body2" Class="mud-text-secondary">@FormatPrice(OrderVatAmount)+</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,92 @@
|
|||||||
|
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!;
|
||||||
|
[Inject] private VATService VAT { get; set; } = default!;
|
||||||
|
|
||||||
|
private DiscountOrderDetail? _order;
|
||||||
|
private bool _loading = true;
|
||||||
|
private string? _paymentMessage;
|
||||||
|
private MudBlazor.Severity _paymentSeverity;
|
||||||
|
|
||||||
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
|
{
|
||||||
|
if (firstRender)
|
||||||
|
{
|
||||||
|
await VAT.LoadAsync();
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>مبلغ قبل از مالیات (جمع کل - اعتبار)</summary>
|
||||||
|
private long PreVatGateway => _order is null ? 0 : (_order.TotalPrice - _order.DiscountBalanceUsed);
|
||||||
|
|
||||||
|
/// <summary>مالیات بر ارزش افزوده</summary>
|
||||||
|
private long OrderVatAmount => VAT.CalculateVAT(PreVatGateway);
|
||||||
|
|
||||||
|
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,173 @@
|
|||||||
|
@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.Cover"
|
||||||
|
Style="width:100%; aspect-ratio:1/1; 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 -->
|
||||||
|
@if (_isAuthenticated)
|
||||||
|
{
|
||||||
|
<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.body1" Class="fw-bold">
|
||||||
|
@($"{_product.Price:N0}") تومان
|
||||||
|
</MudText>
|
||||||
|
</MudStack>
|
||||||
|
@if (VAT.IsEnabled)
|
||||||
|
{
|
||||||
|
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
||||||
|
<MudText Typo="Typo.body2" Class="mud-text-secondary">مالیات ارزش افزوده (@VAT.VatPercentage%):</MudText>
|
||||||
|
<MudText Typo="Typo.body2" Class="mud-text-secondary">
|
||||||
|
@($"{VAT.CalculateVAT(_product.Price):N0}") تومان
|
||||||
|
</MudText>
|
||||||
|
</MudStack>
|
||||||
|
<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">
|
||||||
|
@($"{VAT.AddVAT(_product.Price):N0}") تومان
|
||||||
|
</MudText>
|
||||||
|
</MudStack>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<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>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudAlert Severity="Severity.Info" Dense="true" Variant="Variant.Outlined" Icon="@Icons.Material.Filled.Lock">
|
||||||
|
برای مشاهده قیمت ابتدا وارد شوید
|
||||||
|
</MudAlert>
|
||||||
|
}
|
||||||
|
|
||||||
|
<!-- 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,85 @@
|
|||||||
|
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!;
|
||||||
|
[Inject] private VATService VAT { get; set; } = default!;
|
||||||
|
[Inject] private GuestActionGate GuestGate { get; set; } = default!;
|
||||||
|
[Inject] private AuthService AuthService { get; set; } = default!;
|
||||||
|
|
||||||
|
private bool _isAuthenticated;
|
||||||
|
|
||||||
|
private DiscountProductDetail? _product;
|
||||||
|
private string _selectedImage = string.Empty;
|
||||||
|
private int _quantity = 1;
|
||||||
|
private bool _loading = true;
|
||||||
|
private bool _addingToCart;
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
_isAuthenticated = await AuthService.IsAuthenticatedAsync();
|
||||||
|
await LoadProductAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
|
{
|
||||||
|
if (firstRender)
|
||||||
|
{
|
||||||
|
await VAT.LoadAsync();
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
var productId = _product.Id;
|
||||||
|
var qty = _quantity;
|
||||||
|
var title = _product.Title;
|
||||||
|
await GuestGate.RunAsync(async () =>
|
||||||
|
{
|
||||||
|
await DiscountCartService.AddAsync(productId, qty);
|
||||||
|
Snackbar.Add($"{title} به سبد خرید اضافه شد", MudBlazor.Severity.Success);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
Snackbar.Add("خطا در افزودن به سبد خرید", MudBlazor.Severity.Error);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_addingToCart = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
@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 (_products.Count == 0)
|
||||||
|
{
|
||||||
|
<MudAlert Severity="Severity.Info" Class="my-4">محصولی یافت نشد.</MudAlert>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<!-- Products Grid -->
|
||||||
|
<MudGrid Spacing="1">
|
||||||
|
@foreach (var p in _products)
|
||||||
|
{
|
||||||
|
<MudItem xs="6" sm="6" md="3"
|
||||||
|
onclick="@(() => NavigateToProduct(p.Id))">
|
||||||
|
<div id="@($"shop-product-{p.Id}")" class="h-100">
|
||||||
|
<MudCard Class="rounded-lg h-100 d-flex flex-column overflow-hidden"
|
||||||
|
Style="cursor:pointer;">
|
||||||
|
<MudCardContent Class="d-flex flex-column pa-1 h-100">
|
||||||
|
<div style="aspect-ratio:1/1;width:100%;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>
|
||||||
|
@if (_isAuthenticated)
|
||||||
|
{
|
||||||
|
<div>
|
||||||
|
<MudText Typo="Typo.subtitle2" Color="Color.Primary">@FormatPrice(p.Price)</MudText>
|
||||||
|
<MudText Typo="Typo.overline" Class="mud-text-secondary" Style="font-size:0.6rem;line-height:1;">(+ ارزش افزوده)</MudText>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudText Typo="Typo.caption" Class="mud-text-secondary">
|
||||||
|
<MudIcon Icon="@Icons.Material.Filled.Lock" Size="Size.Small" Class="me-1"/>برای مشاهده قیمت وارد شوید
|
||||||
|
</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>
|
||||||
|
</div>
|
||||||
|
</MudItem>
|
||||||
|
}
|
||||||
|
</MudGrid>
|
||||||
|
|
||||||
|
@* Lazy Load — بارگذاری بیشتر *@
|
||||||
|
@if (_loadingMore)
|
||||||
|
{
|
||||||
|
<MudStack AlignItems="AlignItems.Center" Class="py-4">
|
||||||
|
<MudProgressCircular Color="Color.Primary" Size="Size.Small" Indeterminate="true"/>
|
||||||
|
<MudText Typo="Typo.caption" Class="mud-text-secondary">بارگذاری بیشتر...</MudText>
|
||||||
|
</MudStack>
|
||||||
|
}
|
||||||
|
else if (_hasMore)
|
||||||
|
{
|
||||||
|
<div class="d-flex justify-center py-4">
|
||||||
|
<MudButton Variant="Variant.Outlined" Color="Color.Primary"
|
||||||
|
StartIcon="@Icons.Material.Filled.ExpandMore"
|
||||||
|
OnClick="LoadMore">
|
||||||
|
نمایش محصولات بیشتر
|
||||||
|
</MudButton>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else if (_products.Count > 0)
|
||||||
|
{
|
||||||
|
<MudText Typo="Typo.caption" Align="Align.Center" Class="py-4 mud-text-secondary">
|
||||||
|
همه @(_products.Count) محصول نمایش داده شد
|
||||||
|
</MudText>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</MudContainer>
|
||||||
@@ -0,0 +1,212 @@
|
|||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using Microsoft.AspNetCore.Components.Routing;
|
||||||
|
using Microsoft.AspNetCore.Components.Web;
|
||||||
|
using Microsoft.JSInterop;
|
||||||
|
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!;
|
||||||
|
[Inject] private GuestActionGate GuestGate { get; set; } = default!;
|
||||||
|
[Inject] private AuthService AuthService { get; set; } = default!;
|
||||||
|
[Inject] private IJSRuntime Js { get; set; } = default!;
|
||||||
|
|
||||||
|
private bool _isAuthenticated;
|
||||||
|
private string _search = string.Empty;
|
||||||
|
private long? _selectedCategoryId;
|
||||||
|
private int _currentPage = 1;
|
||||||
|
private bool _loading;
|
||||||
|
private bool _loadingMore;
|
||||||
|
private bool _hasMore = true;
|
||||||
|
private int _totalCount;
|
||||||
|
private const int PageSize = 12;
|
||||||
|
private const string DefaultSortBy = "price desc";
|
||||||
|
|
||||||
|
private List<DiscountProductCard> _products = new();
|
||||||
|
private List<DiscountCategoryNode> _categories = new();
|
||||||
|
private bool _ignoreNextLocationChange;
|
||||||
|
private bool _pendingScrollRestore;
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
_isAuthenticated = await AuthService.IsAuthenticatedAsync();
|
||||||
|
await DiscountCart.EnsureInitializedAsync();
|
||||||
|
DiscountCart.OnChange += StateHasChanged;
|
||||||
|
Navigation.LocationChanged += HandleLocationChanged;
|
||||||
|
|
||||||
|
_categories = await ProductService.GetCategoriesAsync();
|
||||||
|
ApplyStateFromUri();
|
||||||
|
_loading = true;
|
||||||
|
await LoadPages(_currentPage);
|
||||||
|
_loading = false;
|
||||||
|
_pendingScrollRestore = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
|
{
|
||||||
|
if (_pendingScrollRestore && !_loading && _products.Count > 0)
|
||||||
|
{
|
||||||
|
_pendingScrollRestore = false;
|
||||||
|
var payload = await ShopListScrollRestore.TakeAsync(Js, ShopListScrollRestore.DiscountKey);
|
||||||
|
if (payload is not null)
|
||||||
|
await ShopListScrollRestore.RestoreAsync(Js, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
await base.OnAfterRenderAsync(firstRender);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyStateFromUri()
|
||||||
|
{
|
||||||
|
var state = ShopListQueryState.Parse(Navigation.ToAbsoluteUri(Navigation.Uri));
|
||||||
|
_search = state.Query;
|
||||||
|
_selectedCategoryId = state.CategoryId;
|
||||||
|
_currentPage = state.Pages;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ShopListQueryState CaptureState() => new()
|
||||||
|
{
|
||||||
|
Query = _search,
|
||||||
|
CategoryId = _selectedCategoryId,
|
||||||
|
Pages = Math.Max(1, _currentPage)
|
||||||
|
};
|
||||||
|
|
||||||
|
private void SyncUrl()
|
||||||
|
{
|
||||||
|
var target = CaptureState().ToRelativeUrl(RouteConstants.DiscountStore.Products);
|
||||||
|
var currentPathAndQuery = Navigation.ToAbsoluteUri(Navigation.Uri).PathAndQuery;
|
||||||
|
if (string.Equals(currentPathAndQuery, target, StringComparison.OrdinalIgnoreCase))
|
||||||
|
return;
|
||||||
|
|
||||||
|
_ignoreNextLocationChange = true;
|
||||||
|
Navigation.NavigateTo(target, replace: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoadPages(int pagesToLoad)
|
||||||
|
{
|
||||||
|
_products.Clear();
|
||||||
|
_hasMore = true;
|
||||||
|
pagesToLoad = Math.Max(1, pagesToLoad);
|
||||||
|
var search = string.IsNullOrWhiteSpace(_search) ? null : _search;
|
||||||
|
|
||||||
|
for (var page = 1; page <= pagesToLoad; page++)
|
||||||
|
{
|
||||||
|
var result = await ProductService.GetProductsAsync(
|
||||||
|
page: page,
|
||||||
|
pageSize: PageSize,
|
||||||
|
search: search,
|
||||||
|
categoryId: _selectedCategoryId,
|
||||||
|
sortBy: DefaultSortBy);
|
||||||
|
|
||||||
|
if (page == 1)
|
||||||
|
{
|
||||||
|
_products = result.Products;
|
||||||
|
_totalCount = result.TotalCount;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_products.AddRange(result.Products);
|
||||||
|
}
|
||||||
|
|
||||||
|
_currentPage = page;
|
||||||
|
_hasMore = result.CurrentPage < result.TotalPages;
|
||||||
|
if (!_hasMore)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ReloadFromFilters()
|
||||||
|
{
|
||||||
|
_loading = true;
|
||||||
|
_currentPage = 1;
|
||||||
|
StateHasChanged();
|
||||||
|
await LoadPages(1);
|
||||||
|
SyncUrl();
|
||||||
|
_loading = false;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoadMore()
|
||||||
|
{
|
||||||
|
if (_loadingMore || !_hasMore) return;
|
||||||
|
|
||||||
|
_loadingMore = true;
|
||||||
|
StateHasChanged();
|
||||||
|
|
||||||
|
_currentPage++;
|
||||||
|
var result = await ProductService.GetProductsAsync(
|
||||||
|
page: _currentPage,
|
||||||
|
pageSize: PageSize,
|
||||||
|
search: string.IsNullOrWhiteSpace(_search) ? null : _search,
|
||||||
|
categoryId: _selectedCategoryId,
|
||||||
|
sortBy: DefaultSortBy);
|
||||||
|
_products.AddRange(result.Products);
|
||||||
|
_hasMore = result.CurrentPage < result.TotalPages;
|
||||||
|
SyncUrl();
|
||||||
|
|
||||||
|
_loadingMore = false;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SearchProducts() => await ReloadFromFilters();
|
||||||
|
|
||||||
|
private async Task OnSearchKeyUp(KeyboardEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.Key == "Enter")
|
||||||
|
await ReloadFromFilters();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnCategoryChanged(long? value)
|
||||||
|
{
|
||||||
|
_selectedCategoryId = value;
|
||||||
|
await ReloadFromFilters();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task AddToCart(DiscountProductCard p)
|
||||||
|
{
|
||||||
|
await GuestGate.RunAsync(() => DiscountCart.AddAsync(p.Id));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task NavigateToProduct(long id)
|
||||||
|
{
|
||||||
|
await ShopListScrollRestore.SaveAsync(Js, ShopListScrollRestore.DiscountKey, id);
|
||||||
|
Navigation.NavigateTo($"{RouteConstants.DiscountStore.ProductDetail}{id}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleLocationChanged(object? sender, LocationChangedEventArgs args)
|
||||||
|
{
|
||||||
|
if (_ignoreNextLocationChange)
|
||||||
|
{
|
||||||
|
_ignoreNextLocationChange = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var uri = Navigation.ToAbsoluteUri(args.Location);
|
||||||
|
if (!uri.AbsolutePath.Equals(RouteConstants.DiscountStore.Products, StringComparison.OrdinalIgnoreCase))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var incoming = ShopListQueryState.Parse(uri);
|
||||||
|
if (incoming.Matches(CaptureState()))
|
||||||
|
return;
|
||||||
|
|
||||||
|
_ = InvokeAsync(async () =>
|
||||||
|
{
|
||||||
|
ApplyStateFromUri();
|
||||||
|
_loading = true;
|
||||||
|
await LoadPages(_currentPage);
|
||||||
|
_loading = false;
|
||||||
|
_pendingScrollRestore = true;
|
||||||
|
StateHasChanged();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string FormatPrice(long price) => $"{price:N0} تومان";
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
DiscountCart.OnChange -= StateHasChanged;
|
||||||
|
Navigation.LocationChanged -= HandleLocationChanged;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -116,7 +116,7 @@
|
|||||||
<!-- Contact Support -->
|
<!-- Contact Support -->
|
||||||
<section class="py-12 bg-grey-50">
|
<section class="py-12 bg-grey-50">
|
||||||
<MudContainer MaxWidth="MaxWidth.Large">
|
<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" />
|
<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.h4" Class="mb-3">سوال شما پاسخ داده نشد؟</MudText>
|
||||||
<MudText Typo="Typo.body1" Class="mud-text-secondary mb-6">
|
<MudText Typo="Typo.body1" Class="mud-text-secondary mb-6">
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ public partial class FAQ
|
|||||||
Icon = Icons.Material.Filled.Build,
|
Icon = Icons.Material.Filled.Build,
|
||||||
Questions = new List<FAQQuestion>
|
Questions = new List<FAQQuestion>
|
||||||
{
|
{
|
||||||
new() { Question = "شجرهنامه چگونه کار میکند؟", Answer = "شجرهنامه بصری نمایش سلسله مراتبی تیم شما را نشان میدهد و امکان ردیابی روابط ارجاعی را فراهم میکند." },
|
new() { Question = "سازمان فروش چگونه کار میکند؟", Answer = "سازمان فروش بصری نمایش سلسله مراتبی تیم شما را نشان میدهد و امکان ردیابی روابط ارجاعی را فراهم میکند." },
|
||||||
new() { Question = "گزارشگیری به چه صورت است؟", Answer = "سیستم گزارشهای جامع مالی، عملکردی و آماری ارائه میدهد که قابل فیلتر و دانلود به فرمت Excel است." },
|
new() { Question = "گزارشگیری به چه صورت است؟", Answer = "سیستم گزارشهای جامع مالی، عملکردی و آماری ارائه میدهد که قابل فیلتر و دانلود به فرمت Excel است." },
|
||||||
new() { Question = "آیا از موبایل قابل استفاده است؟", Answer = "بله، اپلیکیشن کاملاً responsive است و تجربه کاربری عالی در موبایل و تبلت ارائه میدهد." }
|
new() { Question = "آیا از موبایل قابل استفاده است؟", Answer = "بله، اپلیکیشن کاملاً responsive است و تجربه کاربری عالی در موبایل و تبلت ارائه میدهد." }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,519 @@
|
|||||||
@attribute [Route(RouteConstants.Main.MainPage)]
|
@attribute [Route(RouteConstants.Main.MainPage)]
|
||||||
@inject NavigationManager Navigation
|
@inject IJSRuntime JS
|
||||||
|
|
||||||
<PageTitle>صفحه اصلی</PageTitle>
|
<PageTitle>صفحه اصلی | کارا بازار سلامت</PageTitle>
|
||||||
|
|
||||||
<!-- HERO -->
|
@* ═══════════════════════════════════════════════
|
||||||
<section id="hero" class="hero-section">
|
1. HERO — Bold, minimal, centered
|
||||||
<MudContainer MaxWidth="MaxWidth.Large" Class="py-16">
|
═══════════════════════════════════════════════ *@
|
||||||
<MudGrid Justify="Justify.Center" Spacing="3">
|
<section class="hero-gradient py-12 py-md-16">
|
||||||
<MudItem xs="12" md="6">
|
<MudContainer MaxWidth="MaxWidth.Medium">
|
||||||
<MudChip T="string" Color="Color.Secondary" Variant="Variant.Filled" Class="mb-3">پیشنهاد ویژه برای شروع</MudChip>
|
<MudStack AlignItems="AlignItems.Center" Spacing="4" Class="text-center">
|
||||||
<MudStack Spacing="2">
|
<MudChip T="string" Color="Color.Default" Variant="Variant.Filled"
|
||||||
<MudText Typo="Typo.h1">
|
Class="pulse-chip hero-chip-glass" Size="Size.Small">
|
||||||
با دعوت از دوستان خود، از خریدهای واقعی پاداش بگیرید
|
باشگاه مشتریان KBS کارا بازار سلامت
|
||||||
</MudText>
|
</MudChip>
|
||||||
<MudText Typo="Typo.body1" Class="mud-text-secondary mb-6">
|
|
||||||
ثبتنام آسان، تجربهٔ سریع و شفاف. همین امروز حساب بساز و با دعوت دوستانت، امتیاز و پاداش دریافت کن.
|
|
||||||
</MudText>
|
|
||||||
|
|
||||||
<MudStack Row="true" Spacing="3" Class="mb-2">
|
<MudText Typo="Typo.h1" Class="hero-title" Style="max-width:680px;">
|
||||||
<MudButton Variant="Variant.Outlined"
|
@(_pageData?.HeroTitle ?? "رشد تیم، فروش واقعی، پاداش شفاف")
|
||||||
Color="Color.Primary"
|
</MudText>
|
||||||
Size="Size.Large"
|
|
||||||
Class="mud-ripple rounded-pill"
|
<MudText Typo="Typo.body1" Class="hero-subtitle" Style="max-width:520px;">
|
||||||
OnClick="@(() => Navigation.NavigateTo("/pricing"))">مشاهده قیمتها</MudButton>
|
@(_pageData?.HeroSubtitle ?? "با دعوت از دوستان، از خریدهای واقعی محصولات سلامت پاداش بگیرید. ثبتنام سریع، داشبورد لحظهای.")
|
||||||
<MudButton Variant="Variant.Filled"
|
</MudText>
|
||||||
Color="Color.Primary"
|
|
||||||
Size="Size.Large"
|
<MudStack Row="true" Spacing="2" Class="mt-2 flex-wrap" Justify="Justify.Center">
|
||||||
Class="mud-ripple rounded-pill"
|
<MudButton Variant="Variant.Filled"
|
||||||
OnClick="@(() => NavigateToRegistrationWizard())">شروع کن</MudButton>
|
Class="rounded-pill hero-cta-primary"
|
||||||
</MudStack>
|
Size="Size.Large"
|
||||||
<MudDivider Class="my-2" Style="width: 90%;" />
|
OnClick="NavigateToRegistrationWizard">
|
||||||
<MudStack Row="true" Spacing="1">
|
@(_settings?.HeroButtonPrimaryText ?? "شروع رایگان")
|
||||||
<MudChip T="string"
|
</MudButton>
|
||||||
Color="Color.Info"
|
<MudButton Variant="Variant.Outlined"
|
||||||
Variant="Variant.Outlined"
|
Class="rounded-pill hero-cta-outline"
|
||||||
Class="mb-3">ثبتنام زیر ۲ دقیقه</MudChip>
|
Size="Size.Large"
|
||||||
<MudChip T="string"
|
OnClick="@(() => Navigation.NavigateTo("/blog"))">
|
||||||
Color="Color.Info"
|
@(_settings?.HeroButtonSecondaryText ?? "آخرین اخبار")
|
||||||
Variant="Variant.Outlined"
|
</MudButton>
|
||||||
Class="mb-3">پشتیبانی ۷×۲۴</MudChip>
|
</MudStack>
|
||||||
<MudChip T="string"
|
|
||||||
Color="Color.Info"
|
@* ── Trust badges ── *@
|
||||||
Variant="Variant.Outlined"
|
<MudStack Row="true" Spacing="4" Class="mt-4 flex-wrap" Justify="Justify.Center" AlignItems="AlignItems.Center">
|
||||||
Class="mb-3">پرداخت ایمن</MudChip>
|
@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>
|
||||||
|
</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>
|
</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);">
|
2. HOW IT WORKS — 3 numbered steps
|
||||||
<MudImage Src="images/team-meeting.jpg"
|
═══════════════════════════════════════════════ *@
|
||||||
Alt="جلسه تیم فروش"
|
<section class="section-landing">
|
||||||
ObjectFit="ObjectFit.Cover"
|
<MudContainer MaxWidth="MaxWidth.Large">
|
||||||
ObjectPosition="ObjectPosition.Center"
|
<div class="text-center mb-8 fade-in-up">
|
||||||
Style="width:100%"
|
<MudText Typo="Typo.h3">@(_settings?.StepsTitle ?? "سه گام تا شروع")</MudText>
|
||||||
Class="rounded-xl" />
|
<MudText Typo="Typo.body1" Class="mud-text-secondary mt-2">
|
||||||
</MudPaper>
|
بدون پیچیدگی، سریع و آسان
|
||||||
</MudItem>
|
</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>
|
</MudGrid>
|
||||||
</MudContainer>
|
</MudContainer>
|
||||||
</section>
|
</section>
|
||||||
<MudStack Spacing="4">
|
|
||||||
|
|
||||||
<!-- WHYUS -->
|
@* ═══════════════════════════════════════════════
|
||||||
<section class="whyus-section">
|
3. FEATURES — 6 cards with icons
|
||||||
<MudContainer MaxWidth="MaxWidth.Large" Class="whyus-section">
|
═══════════════════════════════════════════════ *@
|
||||||
|
<section class="section-landing" style="background:var(--mud-palette-background-gray);">
|
||||||
<MudText Typo="Typo.h4" GutterBottom="true">
|
<MudContainer MaxWidth="MaxWidth.Large">
|
||||||
چرا کاربران ما را انتخاب میکنند؟
|
<div class="text-center mb-8 fade-in-up">
|
||||||
</MudText>
|
<MudText Typo="Typo.h3">@(_settings?.FeaturesTitle ?? "چرا کارا بازار سلامت؟")</MudText>
|
||||||
<MudText Typo="Typo.subtitle1" Class="mud-text-secondary mb-8">
|
<MudText Typo="Typo.body1" Class="mud-text-secondary mt-2" Style="max-width:540px;margin:0 auto;">
|
||||||
سه دلیل روشن برای شروع امروز.
|
ابزارهایی ساده و قدرتمند برای رشد کسبوکار شما
|
||||||
</MudText>
|
</MudText>
|
||||||
|
</div>
|
||||||
|
|
||||||
<MudGrid Spacing="3" Justify="Justify.Center">
|
<MudGrid Spacing="3" Justify="Justify.Center">
|
||||||
|
@foreach (var (icon, title, desc, delay) in _features)
|
||||||
|
{
|
||||||
<MudItem xs="12" sm="6" md="4">
|
<MudItem xs="12" sm="6" md="4">
|
||||||
<MudCard Class="feature-card rounded-xl pa-4" Outlined="true">
|
<MudPaper Elevation="0" Class="pa-5 rounded-xl feature-card-v2 fade-in-up" data-delay="@delay">
|
||||||
<MudStack>
|
<MudStack Spacing="2">
|
||||||
<MudStack Row="true">
|
<MudAvatar Color="Color.Primary" Variant="Variant.Filled" Size="Size.Medium" Class="mb-1">
|
||||||
<MudIconButton Icon="@Icons.Material.Outlined.VerifiedUser"
|
<MudIcon Icon="@icon" />
|
||||||
Variant="Variant.Outlined"
|
</MudAvatar>
|
||||||
Color="Color.Primary"
|
<MudText Typo="Typo.h6">@title</MudText>
|
||||||
Size="Size.Small" />
|
<MudText Typo="Typo.body2" Class="mud-text-secondary">@desc</MudText>
|
||||||
<MudText Typo="Typo.h6" Align="Align.Center" Class="mb-2">ثبتنام سریع و ساده</MudText>
|
|
||||||
</MudStack>
|
|
||||||
|
|
||||||
<MudText Class="ps-10 mud-text-secondary">
|
|
||||||
در چند گام کوتاه، حسابت را بساز و شروع کن.
|
|
||||||
</MudText>
|
|
||||||
</MudStack>
|
</MudStack>
|
||||||
</MudCard>
|
</MudPaper>
|
||||||
</MudItem>
|
</MudItem>
|
||||||
|
}
|
||||||
|
</MudGrid>
|
||||||
|
</MudContainer>
|
||||||
|
</section>
|
||||||
|
|
||||||
<MudItem xs="12" sm="6" md="4">
|
@* ═══════════════════════════════════════════════
|
||||||
<MudCard Class="feature-card rounded-xl pa-4" Outlined="true">
|
4. STATS — Counter cards
|
||||||
<MudStack>
|
═══════════════════════════════════════════════ *@
|
||||||
<MudStack Row="true">
|
<section class="section-landing">
|
||||||
<MudIconButton Icon="@Icons.Material.Outlined.StarBorder"
|
<MudContainer MaxWidth="MaxWidth.Medium">
|
||||||
Variant="Variant.Outlined"
|
<MudGrid Spacing="3" Justify="Justify.Center">
|
||||||
Color="Color.Primary"
|
@foreach (var stat in _stats)
|
||||||
Size="Size.Small" />
|
{
|
||||||
<MudText Typo="Typo.h6" Align="Align.Center" Class="mb-2">مزایا و پاداشهای شفاف</MudText>
|
<MudItem xs="6" sm="3">
|
||||||
</MudStack>
|
<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 Class="ps-10 mud-text-secondary">
|
<MudText Typo="Typo.caption" Class="mud-text-secondary mt-1">@stat.Label</MudText>
|
||||||
قوانین روشن، دسترسی آسان به سوابق و گزارشها.
|
</MudPaper>
|
||||||
</MudText>
|
|
||||||
</MudStack>
|
|
||||||
</MudCard>
|
|
||||||
</MudItem>
|
</MudItem>
|
||||||
|
}
|
||||||
|
</MudGrid>
|
||||||
|
</MudContainer>
|
||||||
|
</section>
|
||||||
|
|
||||||
<MudItem xs="12" sm="6" md="4">
|
@* ═══════════════════════════════════════════════
|
||||||
<MudCard Class="feature-card rounded-xl pa-4" Outlined="true">
|
1c. TOP-SELLING REGULAR PRODUCTS
|
||||||
<MudStack>
|
═══════════════════════════════════════════════ *@
|
||||||
<MudStack Row="true">
|
@if (_loadingTopProducts)
|
||||||
<MudIconButton Icon="@Icons.Material.Outlined.Devices"
|
{
|
||||||
Variant="Variant.Outlined"
|
<section class="section-landing" style="background:var(--mud-palette-background-gray);">
|
||||||
Color="Color.Primary"
|
<MudContainer MaxWidth="MaxWidth.Large">
|
||||||
Size="Size.Small" />
|
<MudStack AlignItems="AlignItems.Center" Class="py-4">
|
||||||
<MudText Typo="Typo.h6" Align="Align.Center" Class="mb-2">اپلیکیشن واکنشگرا</MudText>
|
<MudProgressCircular Color="Color.Primary" Indeterminate="true" Size="Size.Small" />
|
||||||
</MudStack>
|
</MudStack>
|
||||||
|
|
||||||
<MudText Class="ps-10 mud-text-secondary">
|
|
||||||
تجربهای روان در موبایل و دسکتاپ.
|
|
||||||
</MudText>
|
|
||||||
</MudStack>
|
|
||||||
</MudCard>
|
|
||||||
</MudItem>
|
|
||||||
</MudGrid>
|
|
||||||
|
|
||||||
</MudContainer>
|
</MudContainer>
|
||||||
</section>
|
</section>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
@if (_topRegularProducts.Any())
|
||||||
|
{
|
||||||
|
<section class="section-landing" style="background:var(--mud-palette-background-gray);">
|
||||||
|
<MudContainer MaxWidth="MaxWidth.Large">
|
||||||
|
<div class="text-center mb-6 fade-in-up">
|
||||||
|
<MudText Typo="Typo.h3">محصولات پرفروش فروشگاه</MudText>
|
||||||
|
<MudText Typo="Typo.body1" Class="mud-text-secondary mt-2">
|
||||||
|
محبوبترین محصولات کارا بازار سلامت
|
||||||
|
</MudText>
|
||||||
|
</div>
|
||||||
|
|
||||||
@* <!-- FEATURES: معرفی پکیجها -->
|
<MudGrid Spacing="2" Justify="Justify.FlexStart">
|
||||||
<section id="features" class="py-20">
|
@foreach (var p in _topRegularProducts)
|
||||||
<MudContainer MaxWidth="MaxWidth.Large">
|
|
||||||
<MudText Typo="Typo.h4" GutterBottom="true">
|
|
||||||
پکیجهای سرمایه گذاری
|
|
||||||
</MudText>
|
|
||||||
<MudText Typo="Typo.subtitle1" Class="mud-text-secondary mb-8">
|
|
||||||
بر اساس اندازهٔ تیم خود انتخاب کنید.
|
|
||||||
</MudText>
|
|
||||||
@if (_isLoadingPackages)
|
|
||||||
{
|
|
||||||
<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>
|
|
||||||
}
|
|
||||||
else if (_packs.Any())
|
|
||||||
{
|
|
||||||
<MudGrid Spacing="4" Justify="Justify.Center" Class="stretch-grid">
|
|
||||||
@foreach (var p in _packs)
|
|
||||||
{
|
{
|
||||||
<MudItem xs="12" md="4" Class="d-flex">
|
<MudItem xs="6" sm="6" md="4">
|
||||||
<MudCard>
|
<MudCard Class="rounded-lg h-100 d-flex flex-column overflow-hidden landing-product-card"
|
||||||
<MudCardMedia Image="@p.Image"
|
Style="cursor:pointer;"
|
||||||
Title="@p.Title"
|
@onclick="() => NavigateToRegularProduct(p.Id)">
|
||||||
Height="300" />
|
<MudCardContent Class="d-flex flex-column pa-1 h-100">
|
||||||
<MudCardContent>
|
<div style="aspect-ratio:1/1;width:100%;background-image:url('@p.ImageUrl');background-size:cover;background-position:center;border-radius:0.5rem;position:relative;">
|
||||||
<MudText Typo="Typo.h5" Class="mb-3">@(p.Title)</MudText>
|
@if (p.RemainingCount <= 0)
|
||||||
@((MarkupString)p.Body)
|
{
|
||||||
|
<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>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div class="pa-1 flex-grow-1 d-flex flex-column justify-space-between">
|
||||||
|
<MudText Typo="Typo.subtitle1" Style="display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;">@p.Title</MudText>
|
||||||
|
@if (_isAuthenticated)
|
||||||
|
{
|
||||||
|
<MudText Typo="Typo.subtitle2" Color="Color.Primary">@FormatPrice(p.Price)</MudText>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudText Typo="Typo.caption" Class="mud-text-secondary">
|
||||||
|
<MudIcon Icon="@Icons.Material.Filled.Lock" Size="Size.Small" Class="me-1"/>برای مشاهده قیمت وارد شوید
|
||||||
|
</MudText>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
</MudCardContent>
|
</MudCardContent>
|
||||||
<MudCardActions Class="ma-2">
|
<MudCardActions Class="mt-auto pa-2" @onclick:stopPropagation="true">
|
||||||
<MudText Typo="Typo.h5"
|
<MudButton Variant="Variant.Filled" Color="Color.Primary" FullWidth="true"
|
||||||
Color="Color.Success"
|
StartIcon="@Icons.Material.Filled.AddShoppingCart"
|
||||||
Align="Align.End">@(p.Price.ToThousands().ToCurrencyUnitIRT())</MudText>
|
Disabled="@(p.RemainingCount <= 0)"
|
||||||
<MudSpacer />
|
OnClick="@(() => AddRegularToCart(p))">
|
||||||
<MudButton Variant="Variant.Filled"
|
@(p.RemainingCount <= 0 ? "ناموجود" : "افزودن به سبد")
|
||||||
Color="Color.Primary"
|
</MudButton>
|
||||||
OnClick="@(() => NavigateToPackage(p.Id))">مشاهده جزئیات</MudButton>
|
|
||||||
</MudCardActions>
|
</MudCardActions>
|
||||||
</MudCard>
|
</MudCard>
|
||||||
</MudItem>
|
</MudItem>
|
||||||
}
|
}
|
||||||
</MudGrid>
|
</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">
|
<div class="text-center mt-6">
|
||||||
<ItemOpposite>
|
<MudButton Variant="Variant.Outlined" Color="Color.Primary" Class="rounded-pill"
|
||||||
<MudText Color="Color.Success" Typo="Typo.h5">دعوت دوستان</MudText>
|
Href="@RouteConstants.Store.Products"
|
||||||
</ItemOpposite>
|
EndIcon="@Icons.Material.Filled.ArrowBack">
|
||||||
<ItemContent>
|
مشاهده همه محصولات
|
||||||
<MudText Align="Align.End" Color="Color.Success" Typo="Typo.h6" GutterBottom="true">مرحله دوم</MudText>
|
</MudButton>
|
||||||
<MudText Align="Align.End">لینک دعوتت را به اشتراک بگذار.</MudText>
|
</div>
|
||||||
</ItemContent>
|
</MudContainer>
|
||||||
</MudTimelineItem>
|
</section>
|
||||||
|
}
|
||||||
|
|
||||||
<MudTimelineItem Color="Color.Error" Size="Size.Small">
|
@* ═══════════════════════════════════════════════
|
||||||
<ItemOpposite>
|
1d. TOP-SELLING DISCOUNT STORE PRODUCTS
|
||||||
<MudText Color="Color.Error" Typo="Typo.h5">دریافت پاداش</MudText>
|
═══════════════════════════════════════════════ *@
|
||||||
</ItemOpposite>
|
@if (_topDiscountProducts.Any())
|
||||||
<ItemContent>
|
{
|
||||||
<MudText Color="Color.Error" Typo="Typo.h6" GutterBottom="true">مرحله سوم</MudText>
|
<section class="section-landing">
|
||||||
<MudText>از خریدهای واقعی دوستانت پاداش بگیر.</MudText>
|
<MudContainer MaxWidth="MaxWidth.Large">
|
||||||
</ItemContent>
|
<div class="text-center mb-6 fade-in-up">
|
||||||
</MudTimelineItem>
|
<MudText Typo="Typo.h3">محصولات پرفروش فروشگاه اعتباری</MudText>
|
||||||
</MudTimeline>
|
<MudText Typo="Typo.body1" Class="mud-text-secondary mt-2">
|
||||||
</MudPaper>
|
محصولات ویژه اعضای باشگاه با پرداخت اعتباری
|
||||||
</MudStack>
|
</MudText>
|
||||||
</MudItem>
|
</div>
|
||||||
<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>
|
|
||||||
|
|
||||||
</MudGrid>
|
<MudGrid Spacing="2" Justify="Justify.FlexStart">
|
||||||
|
@foreach (var p in _topDiscountProducts)
|
||||||
</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">
|
<MudItem xs="6" sm="6" md="4">
|
||||||
<MudText Typo="Typo.body2">@q.A</MudText>
|
<MudCard Class="rounded-lg h-100 d-flex flex-column overflow-hidden landing-product-card"
|
||||||
</MudExpansionPanel>
|
Style="cursor:pointer;"
|
||||||
|
@onclick="() => NavigateToDiscountProduct(p.Id)">
|
||||||
|
<MudCardContent Class="d-flex flex-column pa-1 h-100">
|
||||||
|
<div style="aspect-ratio:1/1;width:100%;background-image:url('@(string.IsNullOrWhiteSpace(p.ThumbnailUrl) ? p.ImageUrl : 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.Secondary" 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" Style="display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;">@p.Title</MudText>
|
||||||
|
@if (_isAuthenticated)
|
||||||
|
{
|
||||||
|
<div>
|
||||||
|
<MudText Typo="Typo.subtitle2" Color="Color.Primary">@FormatDiscountPrice(p.Price)</MudText>
|
||||||
|
<MudText Typo="Typo.overline" Class="mud-text-secondary" Style="font-size:0.6rem;line-height:1;">(+ ارزش افزوده)</MudText>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudText Typo="Typo.caption" Class="mud-text-secondary">
|
||||||
|
<MudIcon Icon="@Icons.Material.Filled.Lock" Size="Size.Small" Class="me-1"/>برای مشاهده قیمت وارد شوید
|
||||||
|
</MudText>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</MudCardContent>
|
||||||
|
<MudCardActions Class="mt-auto pa-2" @onclick:stopPropagation="true">
|
||||||
|
<MudButton Variant="Variant.Filled" Color="Color.Secondary" FullWidth="true"
|
||||||
|
StartIcon="@Icons.Material.Filled.AddShoppingCart"
|
||||||
|
Disabled="@(p.RemainingCount <= 0)"
|
||||||
|
OnClick="@(() => AddDiscountToCart(p))">
|
||||||
|
@(p.RemainingCount <= 0 ? "ناموجود" : "افزودن به سبد")
|
||||||
|
</MudButton>
|
||||||
|
</MudCardActions>
|
||||||
|
</MudCard>
|
||||||
|
</MudItem>
|
||||||
}
|
}
|
||||||
</MudExpansionPanels>
|
</MudGrid>
|
||||||
|
|
||||||
|
<div class="text-center mt-6">
|
||||||
|
<MudButton Variant="Variant.Outlined" Color="Color.Secondary" Class="rounded-pill"
|
||||||
|
Href="@RouteConstants.DiscountStore.Products"
|
||||||
|
EndIcon="@Icons.Material.Filled.ArrowBack">
|
||||||
|
مشاهده همه محصولات اعتباری
|
||||||
|
</MudButton>
|
||||||
|
</div>
|
||||||
|
</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 />
|
||||||
|
<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>
|
||||||
|
</MudItem>
|
||||||
|
}
|
||||||
|
</MudGrid>
|
||||||
|
|
||||||
|
<div class="text-center mt-6">
|
||||||
|
<MudButton Variant="Variant.Outlined" Color="Color.Primary" Class="rounded-pill"
|
||||||
|
Href="/blog">
|
||||||
|
مشاهده همه مقالات
|
||||||
|
</MudButton>
|
||||||
</div>
|
</div>
|
||||||
</MudContainer>
|
</MudContainer>
|
||||||
</section>
|
</section>
|
||||||
|
}
|
||||||
|
|
||||||
</MudStack>
|
@* ═══════════════════════════════════════════════
|
||||||
|
6. TESTIMONIALS
|
||||||
|
═══════════════════════════════════════════════ *@
|
||||||
|
@if (_testimonials.Count > 0)
|
||||||
|
{
|
||||||
|
<section class="section-landing">
|
||||||
|
<MudContainer MaxWidth="MaxWidth.Large">
|
||||||
|
<div class="text-center mb-8 fade-in-up">
|
||||||
|
<MudText Typo="Typo.h3">@(_settings?.TestimonialsTitle ?? "اعتماد مشتریان")</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">@(_settings?.FaqTitle ?? "سوالات متداول")</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">
|
||||||
|
@(_settings?.CtaTitle ?? "آماده شروع هستید؟")
|
||||||
|
</MudText>
|
||||||
|
<MudText Typo="Typo.body1" Align="Align.Center" Class="hero-subtitle">
|
||||||
|
@(_settings?.CtaDescription ?? "همین الان ثبتنام کنید و از مزایای کارا بازار سلامت بهرهمند شوید.")
|
||||||
|
</MudText>
|
||||||
|
<MudButton Variant="Variant.Filled"
|
||||||
|
Size="Size.Large"
|
||||||
|
Class="rounded-pill mt-2 hero-cta-primary"
|
||||||
|
OnClick="NavigateToRegistrationWizard">
|
||||||
|
@(_settings?.CtaButtonText ?? "شروع رایگان")
|
||||||
|
</MudButton>
|
||||||
|
</MudStack>
|
||||||
|
</div>
|
||||||
|
</MudContainer>
|
||||||
|
</section>
|
||||||
|
|||||||
@@ -1,44 +1,286 @@
|
|||||||
using FrontOffice.BFF.Package.Protobuf.Protos.Package;
|
using FrontOffice.Main.Utilities;
|
||||||
using FrontOffice.Main.Utilities;
|
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using Microsoft.JSInterop;
|
||||||
using MudBlazor;
|
using MudBlazor;
|
||||||
using System.Security.Cryptography;
|
|
||||||
|
|
||||||
namespace FrontOffice.Main.Pages;
|
namespace FrontOffice.Main.Pages;
|
||||||
|
|
||||||
public partial class Index:IDisposable
|
public partial class Index : IDisposable
|
||||||
{
|
{
|
||||||
[Inject] private PackageContract.PackageContractClient PackageClient { get; set; } = default!;
|
[Inject] private BlogPostService BlogPostService { get; set; } = default!;
|
||||||
[Inject] private MobileNumberEncryptor Encryptor { get; set; }
|
|
||||||
private string? _email;
|
|
||||||
private bool _isLoadingPackages;
|
|
||||||
private List<Pack> _packs = new();
|
|
||||||
[Inject] private AuthService AuthService { get; set; } = default!;
|
[Inject] private AuthService AuthService { get; set; } = default!;
|
||||||
|
[Inject] private SitePageSettingsService PageSettingsService { get; set; } = default!;
|
||||||
|
[Inject] private ProductService ProductService { get; set; } = default!;
|
||||||
|
[Inject] private DiscountProductService DiscountProductService { get; set; } = default!;
|
||||||
|
[Inject] private CartService Cart { get; set; } = default!;
|
||||||
|
[Inject] private DiscountCartService DiscountCart { get; set; } = default!;
|
||||||
|
[Inject] private GuestActionGate GuestGate { get; set; } = default!;
|
||||||
|
[Inject] private VATService VAT { get; set; } = default!;
|
||||||
|
|
||||||
|
private bool _isAuthenticated;
|
||||||
|
|
||||||
|
// ── CMS page data ──
|
||||||
|
private PageSettingsDto? _pageData;
|
||||||
|
private LandingSettings? _settings;
|
||||||
|
|
||||||
|
// ── Top-selling product sections ──
|
||||||
|
private const int LandingTopProductCount = 6;
|
||||||
|
|
||||||
|
private List<Product> _topRegularProducts = new();
|
||||||
|
private List<DiscountProductCard> _topDiscountProducts = new();
|
||||||
|
private bool _loadingTopProducts = true;
|
||||||
|
|
||||||
|
// ── Latest blog posts (loaded from CMS) ──
|
||||||
|
private List<BlogPostCardDto> _latestPosts = new();
|
||||||
|
|
||||||
|
// ── Data lists (populated from DB or fallback) ──
|
||||||
|
private List<(string Icon, string Text)> _trustBadges = new();
|
||||||
|
private List<(string Title, string Desc)> _steps = new();
|
||||||
|
private List<(string Icon, string Title, string Desc, int Delay)> _features = new();
|
||||||
|
private List<StatItem> _stats = new();
|
||||||
|
private List<TestimonialItem> _testimonials = new();
|
||||||
|
private List<QA> _faqs = new();
|
||||||
|
|
||||||
|
// Track whether animations need re-initialization after data load
|
||||||
|
private bool _dataLoaded;
|
||||||
|
private bool _animationsInitialized;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
MainService.OnChangeHandler +=async () =>
|
MainService.OnChangeHandler += OnStateChanged;
|
||||||
|
_isAuthenticated = await AuthService.IsAuthenticatedAsync();
|
||||||
|
|
||||||
|
// Load landing page settings from CMS
|
||||||
|
try
|
||||||
{
|
{
|
||||||
await InvokeAsync(StateHasChanged);
|
_pageData = await PageSettingsService.GetPageAsync("landing");
|
||||||
};
|
if (_pageData != null)
|
||||||
// await LoadPackagesAsync();
|
{
|
||||||
|
_settings = _pageData.GetSettings<LandingSettings>();
|
||||||
//string mobileNumber = "09387342688";
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Fallback: settings remain null → defaults below
|
||||||
|
}
|
||||||
|
|
||||||
//// انکریپت کردن
|
PopulateFromSettings();
|
||||||
//string encrypted = Encryptor.EncryptMobileNumber(mobileNumber);
|
_dataLoaded = true;
|
||||||
//Console.WriteLine($"Encrypted: {encrypted}");
|
|
||||||
|
|
||||||
//// دیکریپت کردن برای تست
|
// Load top-selling in-stock products and blog posts in parallel
|
||||||
//string decrypted = Encryptor.DecryptMobileNumber(encrypted);
|
var topRegTask = ProductService.GetTopSellingAsync(LandingTopProductCount, inStock: true);
|
||||||
//Console.WriteLine($"Decrypted: {decrypted}");
|
var topDiscTask = DiscountProductService.GetTopSellingAsync(LandingTopProductCount, inStock: true);
|
||||||
|
var featuredPostsTask = BlogPostService.GetFeaturedPostsAsync(2);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await Task.WhenAll(topRegTask, topDiscTask, featuredPostsTask);
|
||||||
|
_topRegularProducts = topRegTask.Result.Products;
|
||||||
|
_topDiscountProducts = topDiscTask.Result.Products;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Fallback: sections remain empty
|
||||||
|
}
|
||||||
|
_loadingTopProducts = false;
|
||||||
|
|
||||||
|
// Load latest published blog posts
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_latestPosts = featuredPostsTask.IsCompletedSuccessfully
|
||||||
|
? featuredPostsTask.Result
|
||||||
|
: 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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void PopulateFromSettings()
|
||||||
|
{
|
||||||
|
// ── Trust badges ──
|
||||||
|
if (_settings?.TrustBadges?.Any() == true)
|
||||||
|
{
|
||||||
|
_trustBadges = _settings.TrustBadges
|
||||||
|
.Select(b => (ResolveIcon(b.IconName), b.Text ?? ""))
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_trustBadges = new()
|
||||||
|
{
|
||||||
|
(Icons.Material.Outlined.Timer, "ثبتنام زیر ۲ دقیقه"),
|
||||||
|
(Icons.Material.Outlined.SupportAgent, "پشتیبانی ۷×۲۴"),
|
||||||
|
(Icons.Material.Outlined.Lock, "پرداخت ایمن"),
|
||||||
|
(Icons.Material.Outlined.Verified, "تضمین کیفیت"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Steps ──
|
||||||
|
if (_settings?.Steps?.Any() == true)
|
||||||
|
{
|
||||||
|
_steps = _settings.Steps
|
||||||
|
.Select(s => (s.Title ?? "", s.Description ?? ""))
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_steps = new()
|
||||||
|
{
|
||||||
|
("ثبتنام و احراز هویت", "یک حساب بسازید، شماره موبایل را تأیید و اطلاعات هویتی را تکمیل کنید."),
|
||||||
|
("دعوت دوستان", "لینک دعوت اختصاصی خود را با دوستان و آشنایان به اشتراک بگذارید."),
|
||||||
|
("دریافت پاداش", "از خریدهای واقعی اعضای تیمتان پاداش شفاف و لحظهای دریافت کنید."),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Features ──
|
||||||
|
if (_settings?.Features?.Any() == true)
|
||||||
|
{
|
||||||
|
_features = _settings.Features
|
||||||
|
.Select((f, i) => (ResolveIcon(f.IconName), f.Title ?? "", f.Description ?? "", i * 100))
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_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 ──
|
||||||
|
if (_settings?.Stats?.Any() == true)
|
||||||
|
{
|
||||||
|
_stats = _settings.Stats
|
||||||
|
.Select((s, i) => new StatItem(
|
||||||
|
$"stat-{i}",
|
||||||
|
$"{s.Value}{s.Suffix}",
|
||||||
|
s.Value,
|
||||||
|
s.Suffix ?? "",
|
||||||
|
ResolveColor(s.Color),
|
||||||
|
s.Label ?? ""))
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_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 ──
|
||||||
|
// اگر تنظیمات از CMS لود شده، لیست خالی یعنی ادمین عمداً حذف کرده — بدون fallback hardcode
|
||||||
|
if (_settings is not null)
|
||||||
|
{
|
||||||
|
_testimonials = (_settings.Testimonials ?? [])
|
||||||
|
.Select((t, i) => new TestimonialItem(t.Quote ?? "", t.Name ?? "", t.Role ?? "", i * 150))
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_testimonials = new()
|
||||||
|
{
|
||||||
|
new("با کارا بازار سلامت، محاسبه کارمزدها و پایش تیمها بدون اکسل و دردسر انجام میشود.", "شرکت سینا نت", "مدیر عملیات", 0),
|
||||||
|
new("سازمان فروش بصری و گزارشهای دقیق باعث شد رشد تیم را لحظهای ببینیم.", "هولدینگ آریانا", "مدیر فروش", 150),
|
||||||
|
new("سادگی ثبتنام و شفافیت پاداشها مهمترین مزیت این پلتفرم است.", "گروه بهداشتی نوین", "مدیر توسعه", 300),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── FAQs ──
|
||||||
|
if (_settings?.Faqs?.Any() == true)
|
||||||
|
{
|
||||||
|
_faqs = _settings.Faqs
|
||||||
|
.Select(f => new QA(f.Question ?? "", f.Answer ?? ""))
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_faqs = new()
|
||||||
|
{
|
||||||
|
new("دامنه اختصاصی دارم؛ قابل اتصال است؟", "بله، پشت دامنه و گواهی SSL خودتان مستقر میشود."),
|
||||||
|
new("با دیتابیس خودم کار میکند؟", "کاملاً. SQL Server، PostgreSQL و MySQL پشتیبانی میشود."),
|
||||||
|
new("چه درگاههایی پشتیبانی میشود؟", "Stripe و PayPal یا درگاه اختصاصی از طریق وبهوکها."),
|
||||||
|
new("میتوانم دادهها را خروجی بگیرم؟", "هر زمان از داشبورد ادمین خروجی CSV/Excel بگیرید."),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resolve icon name from SettingsJson to MudBlazor icon string.
|
||||||
|
/// Falls back to a generic icon.
|
||||||
|
/// </summary>
|
||||||
|
private static string ResolveIcon(string? iconName)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(iconName)) return Icons.Material.Outlined.Info;
|
||||||
|
|
||||||
|
// Try to get from MudBlazor Icons via reflection
|
||||||
|
var field = typeof(Icons.Material.Outlined).GetField(iconName,
|
||||||
|
System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
|
||||||
|
if (field != null) return (string)(field.GetValue(null) ?? Icons.Material.Outlined.Info);
|
||||||
|
|
||||||
|
// Also try Filled
|
||||||
|
field = typeof(Icons.Material.Filled).GetField(iconName,
|
||||||
|
System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
|
||||||
|
if (field != null) return (string)(field.GetValue(null) ?? Icons.Material.Outlined.Info);
|
||||||
|
|
||||||
|
return Icons.Material.Outlined.Info;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Color ResolveColor(string? colorName) => colorName?.ToLowerInvariant() switch
|
||||||
|
{
|
||||||
|
"primary" => Color.Primary,
|
||||||
|
"secondary" => Color.Secondary,
|
||||||
|
"success" => Color.Success,
|
||||||
|
"warning" => Color.Warning,
|
||||||
|
"error" => Color.Error,
|
||||||
|
"info" => Color.Info,
|
||||||
|
_ => Color.Default
|
||||||
|
};
|
||||||
|
|
||||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
{
|
{
|
||||||
|
// Init/re-init scroll animations after data is loaded and rendered
|
||||||
|
if ((firstRender || (_dataLoaded && !_animationsInitialized)) && _steps.Any())
|
||||||
|
{
|
||||||
|
_animationsInitialized = true;
|
||||||
|
|
||||||
|
// 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.IsAuthenticatedAsync())
|
||||||
{
|
{
|
||||||
if ((await AuthService.IsCompleteRegisterAsync()))
|
if (await AuthService.IsCompleteRegisterAsync())
|
||||||
{
|
{
|
||||||
Navigation.NavigateTo(RouteConstants.Profile.Index);
|
Navigation.NavigateTo(RouteConstants.Profile.Index);
|
||||||
}
|
}
|
||||||
@@ -47,56 +289,8 @@ public partial class Index:IDisposable
|
|||||||
Navigation.NavigateTo(RouteConstants.Registration.Wizard);
|
Navigation.NavigateTo(RouteConstants.Registration.Wizard);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await base.OnAfterRenderAsync(firstRender);
|
|
||||||
}
|
|
||||||
|
|
||||||
// private async Task LoadPackagesAsync()
|
await base.OnAfterRenderAsync(firstRender);
|
||||||
// {
|
|
||||||
// _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}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void NavigateToRegistrationWizard()
|
private void NavigateToRegistrationWizard()
|
||||||
@@ -104,25 +298,67 @@ public partial class Index:IDisposable
|
|||||||
Navigation.NavigateTo(RouteConstants.Registration.Wizard);
|
Navigation.NavigateTo(RouteConstants.Registration.Wizard);
|
||||||
}
|
}
|
||||||
|
|
||||||
private record Pack(long Id, string Title, string Body, string Image, long Price);
|
private void NavigateToPost(string slug)
|
||||||
|
|
||||||
private record Plan(string Name, string Price, bool Highlight, IEnumerable<string> Features);
|
|
||||||
|
|
||||||
private record QA(string Q, string A);
|
|
||||||
|
|
||||||
private readonly List<QA> _faqs = new()
|
|
||||||
{
|
{
|
||||||
new("دامنهٔ اختصاصی دارم؛ قابل اتصال است؟", "بله، پشت دامنه و گواهی SSL خودتان مستقر میشود."),
|
Navigation.NavigateTo($"/blog/{slug}");
|
||||||
new("با دیتابیس خودم کار میکند؟", "کاملاً. SQL Server، PostgreSQL و MySQL پشتیبانی میشود."),
|
}
|
||||||
new("چه درگاههایی پشتیبانی میشود؟", "Stripe و PayPal یا درگاه اختصاصی از طریق وبهوکها."),
|
|
||||||
new("میتوانم دادهها را خروجی بگیرم؟", "هر زمان از داشبورد ادمین خروجی CSV/Excel بگیرید."),
|
private void NavigateToRegularProduct(long id)
|
||||||
};
|
=> Navigation.NavigateTo(RouteConstants.Store.ProductDetail + id);
|
||||||
|
|
||||||
|
private void NavigateToDiscountProduct(long id)
|
||||||
|
=> Navigation.NavigateTo(RouteConstants.DiscountStore.ProductDetail + id);
|
||||||
|
|
||||||
|
private async Task AddRegularToCart(Product p)
|
||||||
|
=> await GuestGate.RunAsync(() => Cart.Add(p, 1));
|
||||||
|
|
||||||
|
private async Task AddDiscountToCart(DiscountProductCard p)
|
||||||
|
=> await GuestGate.RunAsync(() => DiscountCart.AddAsync(p.Id));
|
||||||
|
|
||||||
|
private string FormatPrice(long price) => $"{VAT.AddVAT(price):N0} تومان";
|
||||||
|
private string FormatDiscountPrice(long price) => $"{price:N0} تومان";
|
||||||
|
|
||||||
|
private async void OnStateChanged()
|
||||||
|
{
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 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);
|
||||||
|
|
||||||
|
// ── SettingsJson DTO ──
|
||||||
|
private class LandingSettings
|
||||||
|
{
|
||||||
|
public string? HeroButtonPrimaryText { get; set; }
|
||||||
|
public string? HeroButtonSecondaryText { get; set; }
|
||||||
|
public List<TrustBadgeItem>? TrustBadges { get; set; }
|
||||||
|
public string? StepsTitle { get; set; }
|
||||||
|
public List<StepItem>? Steps { get; set; }
|
||||||
|
public string? FeaturesTitle { get; set; }
|
||||||
|
public List<FeatureItem>? Features { get; set; }
|
||||||
|
public string? StatsTitle { get; set; }
|
||||||
|
public List<StatSettingItem>? Stats { get; set; }
|
||||||
|
public string? TestimonialsTitle { get; set; }
|
||||||
|
public List<TestimonialSettingItem>? Testimonials { get; set; }
|
||||||
|
public string? FaqTitle { get; set; }
|
||||||
|
public List<FaqItem>? Faqs { get; set; }
|
||||||
|
public string? CtaTitle { get; set; }
|
||||||
|
public string? CtaDescription { get; set; }
|
||||||
|
public string? CtaButtonText { get; set; }
|
||||||
|
public bool? FeaturedBlogEnabled { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TrustBadgeItem { public string? IconName { get; set; } public string? Text { get; set; } }
|
||||||
|
private class StepItem { public string? Title { get; set; } public string? Description { get; set; } }
|
||||||
|
private class FeatureItem { public string? IconName { get; set; } public string? Title { get; set; } public string? Description { get; set; } }
|
||||||
|
private class StatSettingItem { public string? Label { get; set; } public double Value { get; set; } public string? Suffix { get; set; } public string? Color { get; set; } }
|
||||||
|
private class TestimonialSettingItem { public string? Quote { get; set; } public string? Name { get; set; } public string? Role { get; set; } }
|
||||||
|
private class FaqItem { public string? Question { get; set; } public string? Answer { get; set; } }
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
MainService.OnChangeHandler -=async () =>
|
MainService.OnChangeHandler -= OnStateChanged;
|
||||||
{
|
|
||||||
await InvokeAsync(StateHasChanged);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
@attribute [Route(RouteConstants.Licenses.Index)]
|
||||||
|
@inject SitePageSettingsService PageSettingsService
|
||||||
|
|
||||||
|
<PageTitle>مجوزها و گواهینامهها | کارا بازار سلامت</PageTitle>
|
||||||
|
|
||||||
|
@if (_loading)
|
||||||
|
{
|
||||||
|
<MudContainer MaxWidth="MaxWidth.Large" Class="py-16">
|
||||||
|
<LoadingState />
|
||||||
|
</MudContainer>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<!-- Hero Section -->
|
||||||
|
<section class="licenses-hero-section py-16">
|
||||||
|
<MudContainer MaxWidth="MaxWidth.Large">
|
||||||
|
<MudStack Spacing="3" AlignItems="AlignItems.Center" Class="text-center">
|
||||||
|
<MudChip T="string" Color="Color.Secondary" Variant="Variant.Filled">مجوزها و گواهینامهها</MudChip>
|
||||||
|
<MudText Typo="Typo.h2" Class="mb-3">
|
||||||
|
@(_pageData?.HeroTitle ?? "مجوزها و گواهینامههای رسمی")
|
||||||
|
</MudText>
|
||||||
|
<MudText Typo="Typo.body1" Class="mud-text-secondary" Style="max-width: 700px;">
|
||||||
|
@(_pageData?.HeroSubtitle ?? "ما با دریافت مجوزهای رسمی و تأییدیههای معتبر، اطمینان شما را در استفاده از خدمات خود جلب کردهایم.")
|
||||||
|
</MudText>
|
||||||
|
</MudStack>
|
||||||
|
</MudContainer>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<MudStack Spacing="8" Class="pb-12">
|
||||||
|
|
||||||
|
<!-- Licenses Gallery -->
|
||||||
|
<section class="py-12">
|
||||||
|
<MudContainer MaxWidth="MaxWidth.Large">
|
||||||
|
<MudText Typo="Typo.h3" Align="Align.Center" Class="mb-8">مجوزهای ما</MudText>
|
||||||
|
@if (_licenseImages.Any())
|
||||||
|
{
|
||||||
|
<MudGrid Spacing="4" Justify="Justify.Center">
|
||||||
|
@foreach (var license in _licenseImages)
|
||||||
|
{
|
||||||
|
<MudItem xs="6" sm="6" md="4">
|
||||||
|
<MudCard Elevation="2" Class="h-100">
|
||||||
|
<MudCardMedia Image="@license.ImagePath" Height="250" />
|
||||||
|
<MudCardContent>
|
||||||
|
@if (!string.IsNullOrWhiteSpace(license.Title))
|
||||||
|
{
|
||||||
|
<MudText Typo="Typo.h6" Align="Align.Center" Class="mb-1">@license.Title</MudText>
|
||||||
|
}
|
||||||
|
@if (!string.IsNullOrWhiteSpace(license.Description))
|
||||||
|
{
|
||||||
|
<MudText Typo="Typo.body2" Align="Align.Center" Class="mud-text-secondary">@license.Description</MudText>
|
||||||
|
}
|
||||||
|
</MudCardContent>
|
||||||
|
@if (!string.IsNullOrWhiteSpace(license.LinkUrl))
|
||||||
|
{
|
||||||
|
<MudCardActions Class="justify-center">
|
||||||
|
<MudButton Variant="Variant.Text"
|
||||||
|
Color="Color.Primary"
|
||||||
|
Href="@license.LinkUrl"
|
||||||
|
Target="_blank"
|
||||||
|
StartIcon="@Icons.Material.Filled.OpenInNew">
|
||||||
|
مشاهده جزئیات
|
||||||
|
</MudButton>
|
||||||
|
</MudCardActions>
|
||||||
|
}
|
||||||
|
</MudCard>
|
||||||
|
</MudItem>
|
||||||
|
}
|
||||||
|
</MudGrid>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudAlert Severity="Severity.Info" Variant="Variant.Text" Class="my-8" Dense="true">
|
||||||
|
اطلاعات مجوزها بهزودی بارگذاری خواهد شد.
|
||||||
|
</MudAlert>
|
||||||
|
}
|
||||||
|
</MudContainer>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- CTA Section -->
|
||||||
|
<section class="py-12">
|
||||||
|
<MudContainer MaxWidth="MaxWidth.Medium">
|
||||||
|
<MudPaper Elevation="3" Class="pa-8 rounded-xl text-center">
|
||||||
|
<MudIcon Icon="@Icons.Material.Filled.VerifiedUser" 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">
|
||||||
|
برای دریافت اطلاعات بیشتر درباره مجوزها و گواهینامههای ما، با ما تماس بگیرید.
|
||||||
|
</MudText>
|
||||||
|
<MudButton Variant="Variant.Filled"
|
||||||
|
Color="Color.Primary"
|
||||||
|
Size="Size.Large"
|
||||||
|
OnClick="() => Navigation.NavigateTo(RouteConstants.Contact.Index)">
|
||||||
|
تماس با ما
|
||||||
|
</MudButton>
|
||||||
|
</MudPaper>
|
||||||
|
</MudContainer>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</MudStack>
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
using FrontOffice.Main.Utilities;
|
||||||
|
|
||||||
|
namespace FrontOffice.Main.Pages;
|
||||||
|
|
||||||
|
public partial class Licenses
|
||||||
|
{
|
||||||
|
private bool _loading = true;
|
||||||
|
private PageSettingsDto? _pageData;
|
||||||
|
private List<PageSettingsImageDto> _licenseImages = new();
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_pageData = await PageSettingsService.GetPageAsync("licenses");
|
||||||
|
|
||||||
|
if (_pageData != null)
|
||||||
|
{
|
||||||
|
_licenseImages = _pageData.GetImages("licenses");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Fallback: page remains null → hardcoded content will render
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,14 +6,11 @@
|
|||||||
|
|
||||||
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
||||||
<MudStack Spacing="3">
|
<MudStack Spacing="3">
|
||||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
<PageHeader Title="آمار باشگاه توسعه دهندگان" BackHref="@RouteConstants.Profile.Index" />
|
||||||
<MudText Typo="Typo.h5">آمار باشگاه توسعه دهندگان</MudText>
|
|
||||||
<MudButton Variant="Variant.Text" StartIcon="@Icons.Material.Filled.ArrowBack" Href="@RouteConstants.Profile.Index">بازگشت</MudButton>
|
|
||||||
</MudStack>
|
|
||||||
|
|
||||||
@if (_isLoading)
|
@if (_isLoading)
|
||||||
{
|
{
|
||||||
<MudProgressLinear Color="Color.Primary" Indeterminate="true" />
|
<LoadingState Message="در حال دریافت آمار..." />
|
||||||
}
|
}
|
||||||
else if (_statistics is not null)
|
else if (_statistics is not null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,16 +3,7 @@
|
|||||||
<PageTitle>پکیجهای من</PageTitle>
|
<PageTitle>پکیجهای من</PageTitle>
|
||||||
|
|
||||||
<MudContainer MaxWidth="MaxWidth.Large" Class="py-8">
|
<MudContainer MaxWidth="MaxWidth.Large" Class="py-8">
|
||||||
<!-- Breadcrumb -->
|
<PageHeader Title="پکیجهای من" BackHref="@RouteConstants.Profile.Index" />
|
||||||
<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>
|
|
||||||
|
|
||||||
@if (_isLoading)
|
@if (_isLoading)
|
||||||
{
|
{
|
||||||
@@ -29,7 +20,7 @@
|
|||||||
<MudIcon Icon="@Icons.Material.Filled.CardGiftcard" Size="Size.Large" Color="Color.Primary" Style="font-size: 80px;" />
|
<MudIcon Icon="@Icons.Material.Filled.CardGiftcard" Size="Size.Large" Color="Color.Primary" Style="font-size: 80px;" />
|
||||||
<MudText Typo="Typo.h5">شما هنوز پکیجی خریداری نکردهاید</MudText>
|
<MudText Typo="Typo.h5">شما هنوز پکیجی خریداری نکردهاید</MudText>
|
||||||
<MudText Typo="Typo.body1" Class="mud-text-secondary" Style="max-width: 500px;">
|
<MudText Typo="Typo.body1" Class="mud-text-secondary" Style="max-width: 500px;">
|
||||||
با خرید پکیج طلایی، به باشگاه مشتریان بپیوندید و از مزایای ویژه مانند پاداش هفتگی و تیمسازی بهرهمند شوید.
|
با خرید پکیج، به باشگاه مشتریان بپیوندید و از مزایای ویژه مانند پاداش هفتگی و تیمسازی بهرهمند شوید.
|
||||||
</MudText>
|
</MudText>
|
||||||
<MudButton Variant="Variant.Filled"
|
<MudButton Variant="Variant.Filled"
|
||||||
Color="Color.Primary"
|
Color="Color.Primary"
|
||||||
@@ -43,7 +34,76 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@* ── G3: راهنمای پکیجهای من (Q28) ── *@
|
||||||
|
<MudAlert Severity="Severity.Info" Variant="Variant.Text" Dense="true" Class="mb-2" Icon="@Icons.Material.Filled.Info">
|
||||||
|
بعد از تکمیل چرخه کیفپول جادویی، میتوانید مجدداً پکیج خریداری کرده و دور جدیدی را آغاز کنید.
|
||||||
|
</MudAlert>
|
||||||
|
|
||||||
<MudGrid Spacing="4">
|
<MudGrid Spacing="4">
|
||||||
|
|
||||||
|
@* Re-Purchase / Cycle Progress Section *@
|
||||||
|
@if (_canRepurchase)
|
||||||
|
{
|
||||||
|
<MudItem xs="12">
|
||||||
|
<MudAlert Severity="Severity.Success" Icon="@Icons.Material.Filled.Celebration" Class="mb-0">
|
||||||
|
<MudStack Row="true" AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween" Style="width:100%">
|
||||||
|
<MudText Typo="Typo.body1">
|
||||||
|
🎉 چرخه جادویی تکمیل شد! میتوانید پکیج جدید بخرید.
|
||||||
|
</MudText>
|
||||||
|
<MudButton Variant="Variant.Filled"
|
||||||
|
Color="Color.Primary"
|
||||||
|
StartIcon="@Icons.Material.Filled.ShoppingCart"
|
||||||
|
OnClick="@(() => Navigation.NavigateTo(RouteConstants.Package.List))">
|
||||||
|
خرید پکیج جدید
|
||||||
|
</MudButton>
|
||||||
|
</MudStack>
|
||||||
|
</MudAlert>
|
||||||
|
</MudItem>
|
||||||
|
}
|
||||||
|
else if (_magicStatus != null && _magicStatus.WalletMode == 1)
|
||||||
|
{
|
||||||
|
<MudItem xs="12">
|
||||||
|
<MudPaper Elevation="2" Class="pa-6">
|
||||||
|
<MudStack Spacing="3">
|
||||||
|
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
|
||||||
|
<MudIcon Icon="@Icons.Material.Filled.AutoAwesome" Color="Color.Warning" />
|
||||||
|
<MudText Typo="Typo.h6">پیشرفت چرخه جادویی</MudText>
|
||||||
|
@if (_magicStatus.PurchaseCycleCount > 0)
|
||||||
|
{
|
||||||
|
<MudChip T="string" Size="Size.Small" Color="Color.Info">دور @(_magicStatus.PurchaseCycleCount + 1)</MudChip>
|
||||||
|
}
|
||||||
|
</MudStack>
|
||||||
|
|
||||||
|
<MudDivider />
|
||||||
|
|
||||||
|
<MudStack Spacing="2">
|
||||||
|
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||||||
|
<MudText Typo="Typo.body2" Class="mud-text-secondary">واریز انجامشده:</MudText>
|
||||||
|
<MudText Typo="Typo.body2">@FormatPrice(_magicStatus.MagicTotalDeposited)</MudText>
|
||||||
|
</MudStack>
|
||||||
|
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||||||
|
<MudText Typo="Typo.body2" Class="mud-text-secondary">سقف واریز:</MudText>
|
||||||
|
<MudText Typo="Typo.body2">@FormatPrice(_magicStatus.MagicMaxDeposit)</MudText>
|
||||||
|
</MudStack>
|
||||||
|
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||||||
|
<MudText Typo="Typo.body2" Class="mud-text-secondary">باقیمانده:</MudText>
|
||||||
|
<MudText Typo="Typo.body2" Color="Color.Warning">@FormatPrice(_magicStatus.MagicRemainingDeposit)</MudText>
|
||||||
|
</MudStack>
|
||||||
|
|
||||||
|
<MudProgressLinear Color="Color.Primary" Value="@GetDepositProgress()" Class="my-2" Rounded="true" Size="Size.Large">
|
||||||
|
<MudText Typo="Typo.caption"><b>@GetDepositProgress()%</b></MudText>
|
||||||
|
</MudProgressLinear>
|
||||||
|
|
||||||
|
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||||||
|
<MudText Typo="Typo.body2" Class="mud-text-secondary">اعتبار دریافتی:</MudText>
|
||||||
|
<MudText Typo="Typo.body2" Color="Color.Success">@FormatPrice(_magicStatus.MagicTotalCredited)</MudText>
|
||||||
|
</MudStack>
|
||||||
|
</MudStack>
|
||||||
|
</MudStack>
|
||||||
|
</MudPaper>
|
||||||
|
</MudItem>
|
||||||
|
}
|
||||||
|
|
||||||
<!-- Package Status Card -->
|
<!-- Package Status Card -->
|
||||||
<MudItem xs="12" md="6">
|
<MudItem xs="12" md="6">
|
||||||
<MudPaper Elevation="2" Class="pa-6 h-100">
|
<MudPaper Elevation="2" Class="pa-6 h-100">
|
||||||
@@ -58,7 +118,7 @@
|
|||||||
<MudStack Spacing="2">
|
<MudStack Spacing="2">
|
||||||
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||||||
<MudText Typo="Typo.body2" Class="mud-text-secondary">نوع پکیج:</MudText>
|
<MudText Typo="Typo.body2" Class="mud-text-secondary">نوع پکیج:</MudText>
|
||||||
<MudText Typo="Typo.body2">پکیج طلایی</MudText>
|
<MudText Typo="Typo.body2">@_userStatus.PackageTitle</MudText>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
|
|
||||||
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||||||
@@ -155,7 +215,7 @@
|
|||||||
<MudStack Spacing="3">
|
<MudStack Spacing="3">
|
||||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
|
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
|
||||||
<MudIcon Icon="@Icons.Material.Filled.Stars" Color="Color.Warning" />
|
<MudIcon Icon="@Icons.Material.Filled.Stars" Color="Color.Warning" />
|
||||||
<MudText Typo="Typo.h6">مزایای پکیج طلایی</MudText>
|
<MudText Typo="Typo.h6">مزایای پکیج</MudText>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
|
|
||||||
<MudDivider />
|
<MudDivider />
|
||||||
@@ -180,7 +240,7 @@
|
|||||||
<MudItem xs="12" sm="6" md="3">
|
<MudItem xs="12" sm="6" md="3">
|
||||||
<MudPaper Outlined="true" Class="pa-4 text-center">
|
<MudPaper Outlined="true" Class="pa-4 text-center">
|
||||||
<MudIcon Icon="@Icons.Material.Filled.ShoppingBag" Color="Color.Secondary" Size="Size.Large" />
|
<MudIcon Icon="@Icons.Material.Filled.ShoppingBag" Color="Color.Secondary" Size="Size.Large" />
|
||||||
<MudText Typo="Typo.subtitle2" Class="mt-2">فروشگاه تخفیفی</MudText>
|
<MudText Typo="Typo.subtitle2" Class="mt-2">فروشگاه اعتباری</MudText>
|
||||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">خرید با تخفیف ویژه</MudText>
|
<MudText Typo="Typo.caption" Class="mud-text-secondary">خرید با تخفیف ویژه</MudText>
|
||||||
</MudPaper>
|
</MudPaper>
|
||||||
</MudItem>
|
</MudItem>
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ public partial class MyPackages : ComponentBase
|
|||||||
[Inject] private PackageService PackageService { get; set; } = default!;
|
[Inject] private PackageService PackageService { get; set; } = default!;
|
||||||
|
|
||||||
private UserPackageStatusDto? _userStatus;
|
private UserPackageStatusDto? _userStatus;
|
||||||
|
private MagicWalletStatus? _magicStatus;
|
||||||
private bool _isLoading = true;
|
private bool _isLoading = true;
|
||||||
|
private bool _canRepurchase = false;
|
||||||
|
|
||||||
private List<BreadcrumbItem> _breadcrumbItems = new()
|
private List<BreadcrumbItem> _breadcrumbItems = new()
|
||||||
{
|
{
|
||||||
@@ -30,6 +32,18 @@ public partial class MyPackages : ComponentBase
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
_userStatus = await PackageService.GetUserPackageStatusAsync();
|
_userStatus = await PackageService.GetUserPackageStatusAsync();
|
||||||
|
|
||||||
|
if (_userStatus?.HasPurchasedPackage == true)
|
||||||
|
{
|
||||||
|
_magicStatus = await WalletService.GetMagicWalletStatusAsync();
|
||||||
|
|
||||||
|
// Cycle complete: wallet back to Normal mode, at least one cycle done,
|
||||||
|
// and deposit ceiling fully used (remaining = 0)
|
||||||
|
_canRepurchase = _magicStatus != null
|
||||||
|
&& _magicStatus.WalletMode == 0 // Normal mode
|
||||||
|
&& _magicStatus.PurchaseCycleCount >= 1 // At least one cycle completed
|
||||||
|
&& _magicStatus.MagicRemainingDeposit == 0; // Deposit ceiling used up
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -67,4 +81,11 @@ public partial class MyPackages : ComponentBase
|
|||||||
{
|
{
|
||||||
return string.Format("{0:N0} تومان", price);
|
return string.Format("{0:N0} تومان", price);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int GetDepositProgress()
|
||||||
|
{
|
||||||
|
if (_magicStatus == null || _magicStatus.MagicMaxDeposit <= 0) return 0;
|
||||||
|
var pct = (int)((_magicStatus.MagicTotalDeposited * 100) / _magicStatus.MagicMaxDeposit);
|
||||||
|
return Math.Min(pct, 100);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,13 +3,12 @@
|
|||||||
<PageTitle>پکیجها</PageTitle>
|
<PageTitle>پکیجها</PageTitle>
|
||||||
|
|
||||||
<MudContainer MaxWidth="MaxWidth.Large" Class="py-8">
|
<MudContainer MaxWidth="MaxWidth.Large" Class="py-8">
|
||||||
<!-- Header -->
|
<PageHeader Title="پکیجهای سرمایهگذاری" BackHref="@RouteConstants.Profile.Index" />
|
||||||
<MudStack Class="mb-6">
|
|
||||||
<MudText Typo="Typo.h4">پکیجهای سرمایهگذاری</MudText>
|
@* ── G1: راهنمای سیستم پکیجبیس (Q28) ── *@
|
||||||
<MudText Typo="Typo.body1" Class="mud-text-secondary">
|
<MudAlert Severity="Severity.Info" Variant="Variant.Text" Dense="true" Class="mb-4" Icon="@Icons.Material.Filled.Info">
|
||||||
با خرید پکیج طلایی، به باشگاه مشتریان بپیوندید و از مزایای ویژه بهرهمند شوید.
|
هر پکیج ویژگیها و مزایای منحصربهفردی دارد. با انتخاب پکیج مناسب، از سقف پاداش و ضریب کیفپول جادویی متفاوتی بهرهمند میشوید.
|
||||||
</MudText>
|
</MudAlert>
|
||||||
</MudStack>
|
|
||||||
|
|
||||||
@if (_isLoading)
|
@if (_isLoading)
|
||||||
{
|
{
|
||||||
@@ -33,7 +32,7 @@
|
|||||||
{
|
{
|
||||||
<MudAlert Severity="Severity.Success" Class="mb-6" Icon="@Icons.Material.Filled.CheckCircle">
|
<MudAlert Severity="Severity.Success" Class="mb-6" Icon="@Icons.Material.Filled.CheckCircle">
|
||||||
<MudText>
|
<MudText>
|
||||||
شما قبلاً پکیج طلایی را خریداری کردهاید.
|
شما قبلاً پکیج را خریداری کردهاید.
|
||||||
@if (_userStatus.IsClubMemberActive)
|
@if (_userStatus.IsClubMemberActive)
|
||||||
{
|
{
|
||||||
<span>عضویت باشگاه شما فعال است.</span>
|
<span>عضویت باشگاه شما فعال است.</span>
|
||||||
@@ -70,20 +69,43 @@
|
|||||||
<MudText Typo="Typo.h6" Color="Color.Success">@package.FormattedPrice</MudText>
|
<MudText Typo="Typo.h6" Color="Color.Success">@package.FormattedPrice</MudText>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
|
|
||||||
<!-- Features Preview -->
|
<!-- Package Highlights -->
|
||||||
<MudStack Spacing="1" Class="mt-2">
|
<MudStack Spacing="1" Class="mt-2">
|
||||||
<MudStack Row="true" Spacing="1" AlignItems="AlignItems.Center">
|
@if (package.SupportsDirectPurchase)
|
||||||
<MudIcon Icon="@Icons.Material.Filled.Check" Size="Size.Small" Color="Color.Primary" />
|
{
|
||||||
<MudText Typo="Typo.caption">عضویت در باشگاه مشتریان</MudText>
|
<MudStack Row="true" Spacing="1" AlignItems="AlignItems.Center">
|
||||||
</MudStack>
|
<MudIcon Icon="@Icons.Material.Filled.Check" Size="Size.Small" Color="Color.Primary" />
|
||||||
<MudStack Row="true" Spacing="1" AlignItems="AlignItems.Center">
|
<MudText Typo="Typo.caption">پرداخت مستقیم</MudText>
|
||||||
<MudIcon Icon="@Icons.Material.Filled.Check" Size="Size.Small" Color="Color.Primary" />
|
</MudStack>
|
||||||
<MudText Typo="Typo.caption">دریافت پاداش هفتگی</MudText>
|
}
|
||||||
</MudStack>
|
@if (package.SupportsDayaPurchase)
|
||||||
<MudStack Row="true" Spacing="1" AlignItems="AlignItems.Center">
|
{
|
||||||
<MudIcon Icon="@Icons.Material.Filled.Check" Size="Size.Small" Color="Color.Primary" />
|
<MudStack Row="true" Spacing="1" AlignItems="AlignItems.Center">
|
||||||
<MudText Typo="Typo.caption">تیمسازی نامحدود</MudText>
|
<MudIcon Icon="@Icons.Material.Filled.Check" Size="Size.Small" Color="Color.Primary" />
|
||||||
</MudStack>
|
<MudText Typo="Typo.caption">پرداخت با اعتبار دایا</MudText>
|
||||||
|
</MudStack>
|
||||||
|
}
|
||||||
|
@if (package.DiscountMultiplier > 0)
|
||||||
|
{
|
||||||
|
<MudStack Row="true" Spacing="1" AlignItems="AlignItems.Center">
|
||||||
|
<MudIcon Icon="@Icons.Material.Filled.Check" Size="Size.Small" Color="Color.Primary" />
|
||||||
|
<MudText Typo="Typo.caption">ضریب اعتبار: @package.DiscountMultiplier.ToString("F1")x</MudText>
|
||||||
|
</MudStack>
|
||||||
|
}
|
||||||
|
@if (package.MagicWalletMultiplier > 0)
|
||||||
|
{
|
||||||
|
<MudStack Row="true" Spacing="1" AlignItems="AlignItems.Center">
|
||||||
|
<MudIcon Icon="@Icons.Material.Filled.Check" Size="Size.Small" Color="Color.Primary" />
|
||||||
|
<MudText Typo="Typo.caption">کیف پول جادویی: @package.MagicWalletMultiplier.ToString("F1")x</MudText>
|
||||||
|
</MudStack>
|
||||||
|
}
|
||||||
|
@if (package.IsBasePackage)
|
||||||
|
{
|
||||||
|
<MudStack Row="true" Spacing="1" AlignItems="AlignItems.Center">
|
||||||
|
<MudIcon Icon="@Icons.Material.Filled.Star" Size="Size.Small" Color="Color.Warning" />
|
||||||
|
<MudText Typo="Typo.caption">پکیج پایه</MudText>
|
||||||
|
</MudStack>
|
||||||
|
}
|
||||||
</MudStack>
|
</MudStack>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
</MudCardContent>
|
</MudCardContent>
|
||||||
@@ -106,9 +128,9 @@
|
|||||||
<MudGrid Spacing="4">
|
<MudGrid Spacing="4">
|
||||||
<MudItem xs="12" md="8">
|
<MudItem xs="12" md="8">
|
||||||
<MudStack Spacing="2">
|
<MudStack Spacing="2">
|
||||||
<MudText Typo="Typo.h5" Style="color: white;">چرا پکیج طلایی؟</MudText>
|
<MudText Typo="Typo.h5" Style="color: white;">چرا پکیج بخریم؟</MudText>
|
||||||
<MudText Typo="Typo.body1" Style="color: rgba(255,255,255,0.9);">
|
<MudText Typo="Typo.body1" Style="color: rgba(255,255,255,0.9);">
|
||||||
با خرید پکیج طلایی، علاوه بر دسترسی به محصولات ویژه، میتوانید تیم فروش خود را بسازید
|
با خرید پکیج، علاوه بر دسترسی به محصولات ویژه، میتوانید تیم فروش خود را بسازید
|
||||||
و از پاداشهای هفتگی بهرهمند شوید. هر چه تیم شما گستردهتر، درآمد شما بیشتر!
|
و از پاداشهای هفتگی بهرهمند شوید. هر چه تیم شما گستردهتر، درآمد شما بیشتر!
|
||||||
</MudText>
|
</MudText>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
|
|||||||
@@ -4,26 +4,25 @@
|
|||||||
|
|
||||||
@if (_isLoading)
|
@if (_isLoading)
|
||||||
{
|
{
|
||||||
<MudStack AlignItems="AlignItems.Center" Class="py-16">
|
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
||||||
<MudProgressCircular Color="Color.Primary" Indeterminate="true" Size="Size.Large" />
|
<LoadingState Message="در حال بارگذاری..." />
|
||||||
<MudText Typo="Typo.body1" Class="mud-text-secondary mt-2">در حال بارگذاری...</MudText>
|
</MudContainer>
|
||||||
</MudStack>
|
|
||||||
}
|
}
|
||||||
else if (_package == null)
|
else if (_package == null)
|
||||||
{
|
{
|
||||||
<MudStack AlignItems="AlignItems.Center" Class="py-16">
|
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
||||||
<MudIcon Icon="@Icons.Material.Filled.Error" Size="Size.Large" Color="Color.Error" />
|
<EmptyState Icon="@Icons.Material.Filled.Error"
|
||||||
<MudText Typo="Typo.h5" Class="mt-2">پکیج یافت نشد</MudText>
|
Title="پکیج یافت نشد"
|
||||||
<MudText Typo="Typo.body2" Class="mud-text-secondary">پکیج مورد نظر وجود ندارد یا حذف شده است.</MudText>
|
Description="پکیج مورد نظر وجود ندارد یا حذف شده است."
|
||||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" Class="mt-4" OnClick="() => Navigation.NavigateTo(RouteConstants.Main.MainPage)">
|
ActionText="بازگشت به صفحه اصلی"
|
||||||
بازگشت به صفحه اصلی
|
ActionHref="@RouteConstants.Main.MainPage" />
|
||||||
</MudButton>
|
</MudContainer>
|
||||||
</MudStack>
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<!-- Breadcrumb -->
|
<!-- Breadcrumb -->
|
||||||
<MudContainer MaxWidth="MaxWidth.Large" Class="py-4">
|
<MudContainer MaxWidth="MaxWidth.Large" Class="py-4">
|
||||||
|
<PageHeader Title="جزئیات پکیج" BackHref="@RouteConstants.Package.List" />
|
||||||
<MudBreadcrumbs Items="_breadcrumbItems" />
|
<MudBreadcrumbs Items="_breadcrumbItems" />
|
||||||
</MudContainer>
|
</MudContainer>
|
||||||
|
|
||||||
@@ -36,9 +35,9 @@ else
|
|||||||
<MudStack Class="mb-4">
|
<MudStack Class="mb-4">
|
||||||
<!-- Package Image -->
|
<!-- 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);">
|
<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"
|
Alt="@_package.Title"
|
||||||
Height="250"
|
ImgHeight="250"
|
||||||
ObjectFit="ObjectFit.Cover"
|
ObjectFit="ObjectFit.Cover"
|
||||||
ObjectPosition="ObjectPosition.Center"
|
ObjectPosition="ObjectPosition.Center"
|
||||||
Style="width:100%"
|
Style="width:100%"
|
||||||
@@ -228,9 +227,9 @@ else
|
|||||||
<MudItem xs="12" md="4">
|
<MudItem xs="12" md="4">
|
||||||
<MudPaper Elevation="2" Class="pa-4 d-flex flex-column h-100 cursor-pointer"
|
<MudPaper Elevation="2" Class="pa-4 d-flex flex-column h-100 cursor-pointer"
|
||||||
OnClick="() => NavigateToPackage(relatedPackage.Id)">
|
OnClick="() => NavigateToPackage(relatedPackage.Id)">
|
||||||
<MudImage Src="@relatedPackage.Image"
|
<AppImage Path="@relatedPackage.Image"
|
||||||
Alt="@relatedPackage.Title"
|
Alt="@relatedPackage.Title"
|
||||||
Height="200"
|
ImgHeight="200"
|
||||||
ObjectFit="ObjectFit.Cover"
|
ObjectFit="ObjectFit.Cover"
|
||||||
Class="rounded-xl mb-3" />
|
Class="rounded-xl mb-3" />
|
||||||
<MudText Typo="Typo.h6" Class="mb-2">@relatedPackage.Title</MudText>
|
<MudText Typo="Typo.h6" Class="mb-2">@relatedPackage.Title</MudText>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
using Blazored.LocalStorage;
|
using Blazored.LocalStorage;
|
||||||
using FrontOffice.BFF.Package.Protobuf.Protos.Package;
|
using CMSMicroservice.Protobuf.Protos.Package;
|
||||||
using FrontOffice.Main.Shared;
|
using FrontOffice.Main.Shared;
|
||||||
using FrontOffice.Main.Utilities;
|
using FrontOffice.Main.Utilities;
|
||||||
using Grpc.Core;
|
using Grpc.Core;
|
||||||
@@ -64,36 +64,38 @@ public partial class PackageDetail : IDisposable
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Load package details
|
// Load package details via Customer RPC
|
||||||
var packageResponse = await PackageClient.GetPackageAsync(request: new() { Id = Id}, cancellationToken: _loadCts.Token);
|
var response = await PackageClient.GetCustomerPackageDetailsAsync(
|
||||||
|
new GetCustomerPackageDetailsRequest { PackageId = Id },
|
||||||
|
cancellationToken: _loadCts.Token);
|
||||||
|
|
||||||
if (packageResponse != null)
|
if (response != null)
|
||||||
{
|
{
|
||||||
|
// Build features from API response
|
||||||
|
var features = response.Features?.Select(f => f.Title).ToList() ?? new List<string>();
|
||||||
|
|
||||||
|
// Build specifications from API features (highlighted ones)
|
||||||
|
var specs = response.Features?
|
||||||
|
.Where(f => f.IsHighlighted)
|
||||||
|
.Select(f => new Specification
|
||||||
|
{
|
||||||
|
Name = f.Title,
|
||||||
|
Value = f.Description,
|
||||||
|
Icon = string.IsNullOrEmpty(f.Icon) ? Icons.Material.Filled.Star : f.Icon
|
||||||
|
}).ToList() ?? new List<Specification>();
|
||||||
|
|
||||||
_package = new PackageDetailDto
|
_package = new PackageDetailDto
|
||||||
{
|
{
|
||||||
Id = packageResponse.Id,
|
Id = response.Id,
|
||||||
Title = packageResponse.Title,
|
Title = response.Title,
|
||||||
Body = packageResponse.Description,
|
Body = response.Description,
|
||||||
Image = UrlUtility.DownloadUrl + packageResponse.ImagePath,
|
Image = response.ImagePath ?? string.Empty,
|
||||||
Specifications = new List<Specification>
|
Specifications = specs,
|
||||||
{
|
Features = features,
|
||||||
new() { Name = "ظرفیت", Value = "تا ۲۰۰ عضو", Icon = Icons.Material.Filled.Group },
|
|
||||||
new() { Name = "شجرهنامه", Value = "پیشرفته", Icon = Icons.Material.Filled.AccountTree },
|
|
||||||
new() { Name = "گزارشگیری", Value = "جامع", Icon = Icons.Material.Filled.Analytics },
|
|
||||||
new() { Name = "پشتیبانی", Value = "۲۴ ساعته", Icon = Icons.Material.Filled.Support }
|
|
||||||
},
|
|
||||||
Features = new List<string>
|
|
||||||
{
|
|
||||||
"مدیریت تیم نامحدود",
|
|
||||||
"شجرهنامه بصری",
|
|
||||||
"محاسبه کارمزد خودکار",
|
|
||||||
"گزارشهای مالی",
|
|
||||||
"پشتیبانی اولویتدار"
|
|
||||||
},
|
|
||||||
Pricing = new PricingInfo
|
Pricing = new PricingInfo
|
||||||
{
|
{
|
||||||
OriginalPrice = packageResponse.Price,
|
OriginalPrice = response.Price,
|
||||||
FinalPrice = packageResponse.Price,
|
FinalPrice = response.Price,
|
||||||
HasDiscount = false,
|
HasDiscount = false,
|
||||||
DiscountPercent = 0
|
DiscountPercent = 0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,10 +4,7 @@
|
|||||||
|
|
||||||
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
||||||
<MudStack Spacing="3">
|
<MudStack Spacing="3">
|
||||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
<PageHeader Title="مدیریت آدرسها" BackHref="@RouteConstants.Profile.Index" />
|
||||||
<MudText Typo="Typo.h5">مدیریت آدرسها</MudText>
|
|
||||||
<MudButton Variant="Variant.Text" StartIcon="@Icons.Material.Filled.ArrowBack" Href="@RouteConstants.Profile.Index">بازگشت</MudButton>
|
|
||||||
</MudStack>
|
|
||||||
|
|
||||||
<MudPaper Elevation="2" Class="pa-4 rounded-lg">
|
<MudPaper Elevation="2" Class="pa-4 rounded-lg">
|
||||||
<MudStack Spacing="4">
|
<MudStack Spacing="4">
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using FrontOffice.BFF.UserAddress.Protobuf.Protos.UserAddress;
|
using FrontOffice.Main.Utilities;
|
||||||
|
using CMSMicroservice.Protobuf.Protos.UserAddress;
|
||||||
using FrontOffice.Main.Pages.Profile.Components;
|
using FrontOffice.Main.Pages.Profile.Components;
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
using MudBlazor;
|
using MudBlazor;
|
||||||
@@ -8,8 +9,9 @@ namespace FrontOffice.Main.Pages.Profile;
|
|||||||
public partial class Addresses : ComponentBase
|
public partial class Addresses : ComponentBase
|
||||||
{
|
{
|
||||||
[Inject] private UserAddressContract.UserAddressContractClient UserAddressContract { get; set; } = default!;
|
[Inject] private UserAddressContract.UserAddressContractClient UserAddressContract { get; set; } = default!;
|
||||||
|
[Inject] private AuthService AuthService { get; set; } = default!;
|
||||||
|
|
||||||
private List<GetAllUserAddressByFilterResponseModel> _addresses = new();
|
private List<CustomerAddressModel> _addresses = new();
|
||||||
private bool _isLoadingAddresses;
|
private bool _isLoadingAddresses;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
@@ -22,7 +24,7 @@ public partial class Addresses : ComponentBase
|
|||||||
_isLoadingAddresses = true;
|
_isLoadingAddresses = true;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var response = await UserAddressContract.GetAllUserAddressByFilterAsync(new());
|
var response = await UserAddressContract.GetCustomerAddressesAsync(new());
|
||||||
_addresses = response?.Models?.ToList() ?? new();
|
_addresses = response?.Models?.ToList() ?? new();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -41,10 +43,13 @@ public partial class Addresses : ComponentBase
|
|||||||
var dialog = await DialogService.ShowAsync<AddAddressDialog>("افزودن آدرس جدید");
|
var dialog = await DialogService.ShowAsync<AddAddressDialog>("افزودن آدرس جدید");
|
||||||
var result = await dialog.Result;
|
var result = await dialog.Result;
|
||||||
if (result is not null && !result.Canceled)
|
if (result is not null && !result.Canceled)
|
||||||
|
{
|
||||||
await LoadAddresses();
|
await LoadAddresses();
|
||||||
|
await AuthService.RefreshTokenAsync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task OpenEditAddressDialog(GetAllUserAddressByFilterResponseModel address)
|
private async Task OpenEditAddressDialog(CustomerAddressModel address)
|
||||||
{
|
{
|
||||||
var dialog = await DialogService.ShowAsync<EditAddressDialog>("ویرایش آدرس", new DialogParameters<EditAddressDialog>
|
var dialog = await DialogService.ShowAsync<EditAddressDialog>("ویرایش آدرس", new DialogParameters<EditAddressDialog>
|
||||||
{
|
{
|
||||||
@@ -59,7 +64,7 @@ public partial class Addresses : ComponentBase
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await UserAddressContract.SetAddressAsDefaultAsync(new() { Id = id });
|
await UserAddressContract.SetCustomerDefaultAddressAsync(new() { Id = id });
|
||||||
Snackbar.Add("آدرس پیشفرض تغییر کرد.", Severity.Success);
|
Snackbar.Add("آدرس پیشفرض تغییر کرد.", Severity.Success);
|
||||||
await LoadAddresses();
|
await LoadAddresses();
|
||||||
}
|
}
|
||||||
@@ -76,9 +81,10 @@ public partial class Addresses : ComponentBase
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await UserAddressContract.DeleteUserAddressAsync(new() { Id = id });
|
await UserAddressContract.DeleteCustomerAddressAsync(new() { Id = id });
|
||||||
Snackbar.Add("آدرس حذف شد.", Severity.Success);
|
Snackbar.Add("آدرس حذف شد.", Severity.Success);
|
||||||
await LoadAddresses();
|
await LoadAddresses();
|
||||||
|
await AuthService.RefreshTokenAsync();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,10 +4,7 @@
|
|||||||
|
|
||||||
<MudContainer MaxWidth="MaxWidth.Small" Class="py-6">
|
<MudContainer MaxWidth="MaxWidth.Small" Class="py-6">
|
||||||
<MudStack Spacing="3">
|
<MudStack Spacing="3">
|
||||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
<PageHeader Title="تغییر رمز عبور" BackHref="@RouteConstants.Profile.Settings" />
|
||||||
<MudText Typo="Typo.h5">تغییر رمز عبور</MudText>
|
|
||||||
<MudButton Variant="Variant.Text" StartIcon="@Icons.Material.Filled.ArrowBack" Href="@RouteConstants.Profile.Settings">بازگشت</MudButton>
|
|
||||||
</MudStack>
|
|
||||||
|
|
||||||
<MudPaper Elevation="2" Class="pa-4 rounded-lg">
|
<MudPaper Elevation="2" Class="pa-4 rounded-lg">
|
||||||
<MudStack Spacing="3">
|
<MudStack Spacing="3">
|
||||||
|
|||||||
@@ -0,0 +1,212 @@
|
|||||||
|
@attribute [Route(RouteConstants.Profile.ChargeCreditWallet)]
|
||||||
|
@attribute [Authorize]
|
||||||
|
|
||||||
|
<PageTitle>شارژ کیفپول اصلی</PageTitle>
|
||||||
|
|
||||||
|
<MudContainer MaxWidth="MaxWidth.Medium" Class="py-6">
|
||||||
|
<MudStack Spacing="3">
|
||||||
|
<PageHeader Title="💳 شارژ کیفپول اصلی" BackHref="@RouteConstants.Profile.Wallet" />
|
||||||
|
|
||||||
|
@if (_paymentResult != null)
|
||||||
|
{
|
||||||
|
<MudAlert Severity="@(_paymentResult == "success" ? MudBlazor.Severity.Success : MudBlazor.Severity.Error)"
|
||||||
|
Class="rounded-lg" Variant="Variant.Filled">
|
||||||
|
@if (_paymentResult == "success")
|
||||||
|
{
|
||||||
|
<MudStack Spacing="2">
|
||||||
|
<span>شارژ کیف پول اصلی با موفقیت انجام شد! ✅</span>
|
||||||
|
<MudButton Variant="Variant.Filled"
|
||||||
|
Color="Color.Primary"
|
||||||
|
Size="Size.Small"
|
||||||
|
StartIcon="@Icons.Material.Filled.ShoppingCart"
|
||||||
|
OnClick="ContinueShoppingAsync">
|
||||||
|
ادامه خرید
|
||||||
|
</MudButton>
|
||||||
|
</MudStack>
|
||||||
|
}
|
||||||
|
else if (_paymentResult == "cancelled")
|
||||||
|
{
|
||||||
|
<span>پرداخت توسط شما لغو شد.</span>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span>پرداخت ناموفق بود. لطفاً دوباره تلاش کنید.</span>
|
||||||
|
}
|
||||||
|
</MudAlert>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (_authLoaded && _redirectingToMagic)
|
||||||
|
{
|
||||||
|
<MudAlert Severity="MudBlazor.Severity.Info" Class="rounded-lg" Variant="Variant.Outlined">
|
||||||
|
<MudStack Spacing="2" AlignItems="AlignItems.Center">
|
||||||
|
<MudProgressCircular Size="Size.Small" Indeterminate="true" Color="Color.Primary" />
|
||||||
|
<MudText>
|
||||||
|
کیف پول شما در حالت جادویی است. در حال انتقال به صفحه شارژ کیف پول جادویی...
|
||||||
|
</MudText>
|
||||||
|
<MudButton Size="Size.Small" Variant="Variant.Filled" Color="Color.Primary"
|
||||||
|
Href="@RouteConstants.Profile.MagicWallet"
|
||||||
|
StartIcon="@Icons.Material.Filled.AutoAwesome">
|
||||||
|
رفتن به شارژ کیف جادویی
|
||||||
|
</MudButton>
|
||||||
|
</MudStack>
|
||||||
|
</MudAlert>
|
||||||
|
}
|
||||||
|
else if (_authLoaded && !_isClubMemberActive)
|
||||||
|
{
|
||||||
|
<MudAlert Severity="MudBlazor.Severity.Warning" Class="rounded-lg" Variant="Variant.Outlined">
|
||||||
|
<MudStack Spacing="2">
|
||||||
|
<MudText>
|
||||||
|
برای شارژ کیف پول اصلی ابتدا باید پکیج را خریداری کرده و قرارداد باشگاه مشتریان را امضا کنید.
|
||||||
|
</MudText>
|
||||||
|
<MudStack Row="true" Spacing="1" Class="flex-wrap">
|
||||||
|
@if (!_hasPurchasedPackage)
|
||||||
|
{
|
||||||
|
<MudButton Size="Size.Small" Variant="Variant.Filled" Color="Color.Primary"
|
||||||
|
Href="@RouteConstants.Package.List"
|
||||||
|
StartIcon="@Icons.Material.Filled.CardGiftcard">
|
||||||
|
مشاهده پکیجها
|
||||||
|
</MudButton>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudButton Size="Size.Small" Variant="Variant.Filled" Color="Color.Primary"
|
||||||
|
Href="@RouteConstants.Club.Membership"
|
||||||
|
StartIcon="@Icons.Material.Filled.Handshake">
|
||||||
|
امضای قرارداد باشگاه
|
||||||
|
</MudButton>
|
||||||
|
}
|
||||||
|
<MudButton Size="Size.Small" Variant="Variant.Text"
|
||||||
|
Href="@RouteConstants.Profile.Wallet">
|
||||||
|
بازگشت به کیف پول
|
||||||
|
</MudButton>
|
||||||
|
</MudStack>
|
||||||
|
</MudStack>
|
||||||
|
</MudAlert>
|
||||||
|
}
|
||||||
|
else if (_authLoaded && _needsMagicCeilingConsent && !_allowNormalDespiteMagic && !_redirectingToMagic)
|
||||||
|
{
|
||||||
|
<MudAlert Severity="MudBlazor.Severity.Warning" Class="rounded-lg" Variant="Variant.Outlined">
|
||||||
|
<MudStack Spacing="3">
|
||||||
|
<MudText Typo="Typo.subtitle1">
|
||||||
|
سقف شارژ کیف پول جادویی در این دور پر شده است.
|
||||||
|
</MudText>
|
||||||
|
<MudText Typo="Typo.body2">
|
||||||
|
شارژ از این صفحه بدون ضریب (۱:۱) به موجودی اضافه میشود و ضریب جادویی اعمال نمیشود.
|
||||||
|
برای ادامه، تأیید کنید.
|
||||||
|
</MudText>
|
||||||
|
<MudStack Row="true" Spacing="1" Class="flex-wrap">
|
||||||
|
<MudButton Variant="Variant.Filled"
|
||||||
|
Color="Color.Primary"
|
||||||
|
StartIcon="@Icons.Material.Filled.Check"
|
||||||
|
OnClick="AcceptMagicCeilingConsent">
|
||||||
|
ادامه شارژ بدون ضریب
|
||||||
|
</MudButton>
|
||||||
|
<MudButton Variant="Variant.Outlined"
|
||||||
|
Color="Color.Secondary"
|
||||||
|
StartIcon="@Icons.Material.Filled.AutoAwesome"
|
||||||
|
OnClick="DeclineMagicCeilingConsent">
|
||||||
|
بازگشت به کیف جادویی
|
||||||
|
</MudButton>
|
||||||
|
</MudStack>
|
||||||
|
</MudStack>
|
||||||
|
</MudAlert>
|
||||||
|
}
|
||||||
|
else if (ShowChargeForm)
|
||||||
|
{
|
||||||
|
<MudPaper Elevation="2" Class="pa-5 rounded-lg">
|
||||||
|
<MudText Typo="Typo.h6" Class="mb-3">
|
||||||
|
<MudIcon Icon="@Icons.Material.Filled.AccountBalanceWallet" Class="ml-1" />
|
||||||
|
شارژ موجودی اصلی
|
||||||
|
</MudText>
|
||||||
|
|
||||||
|
@if (_allowNormalDespiteMagic)
|
||||||
|
{
|
||||||
|
<MudAlert Severity="MudBlazor.Severity.Warning" Dense="true" Class="mb-3" Icon="@Icons.Material.Filled.Warning">
|
||||||
|
این شارژ بدون ضریب جادویی است (۱:۱). سقف واریز جادویی شما در این دور پر شده است.
|
||||||
|
</MudAlert>
|
||||||
|
}
|
||||||
|
|
||||||
|
<MudAlert Severity="MudBlazor.Severity.Info" Dense="true" Class="mb-3" Icon="@Icons.Material.Filled.Info">
|
||||||
|
مبلغ وارد شده (به تومان) از طریق درگاه پرداخت به موجودی اصلی شما اضافه میشود.
|
||||||
|
از موجودی اصلی برای خرید از فروشگاه عادی و پرداخت سفارش استفاده میشود.
|
||||||
|
</MudAlert>
|
||||||
|
|
||||||
|
<MudStack Spacing="2">
|
||||||
|
<MudNumericField @bind-Value="_chargeAmount"
|
||||||
|
Label="مبلغ (تومان)"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Min="10_000"
|
||||||
|
Max="1_000_000_000"
|
||||||
|
Format="N0"
|
||||||
|
Adornment="Adornment.Start"
|
||||||
|
AdornmentIcon="@Icons.Material.Filled.Payments"
|
||||||
|
HelperText="حداقل ۱۰,۰۰۰ و حداکثر ۱,۰۰۰,۰۰۰,۰۰۰ تومان" />
|
||||||
|
|
||||||
|
<MudStack Row="true" Spacing="1" Class="flex-wrap">
|
||||||
|
@foreach (var preset in _presetAmounts)
|
||||||
|
{
|
||||||
|
<MudButton Variant="Variant.Outlined" Size="Size.Small"
|
||||||
|
Color="@(_chargeAmount == preset ? Color.Primary : Color.Default)"
|
||||||
|
OnClick="() => _chargeAmount = preset"
|
||||||
|
Class="rounded-pill">
|
||||||
|
@FormatToman(preset)
|
||||||
|
</MudButton>
|
||||||
|
}
|
||||||
|
</MudStack>
|
||||||
|
|
||||||
|
@if (_chargeAmount > 0)
|
||||||
|
{
|
||||||
|
<MudPaper Outlined="true" Class="pa-3 rounded-lg" Style="background: #eff6ff;">
|
||||||
|
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||||||
|
<MudText Typo="Typo.body2">مبلغ پرداخت:</MudText>
|
||||||
|
<MudText Typo="Typo.body2"><strong>@FormatToman(_chargeAmount)</strong></MudText>
|
||||||
|
</MudStack>
|
||||||
|
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||||||
|
<MudText Typo="Typo.body2">موجودی دریافتی:</MudText>
|
||||||
|
<MudText Typo="Typo.body2" Color="Color.Primary"><strong>@FormatToman(_chargeAmount)</strong></MudText>
|
||||||
|
</MudStack>
|
||||||
|
</MudPaper>
|
||||||
|
}
|
||||||
|
|
||||||
|
<MudButton Variant="Variant.Filled"
|
||||||
|
Color="Color.Primary"
|
||||||
|
Size="Size.Large"
|
||||||
|
FullWidth="true"
|
||||||
|
Disabled="@(_isProcessing || _chargeAmount < 10_000)"
|
||||||
|
OnClick="StartCreditCharge"
|
||||||
|
StartIcon="@Icons.Material.Filled.Payment"
|
||||||
|
Class="rounded-lg mt-2">
|
||||||
|
@if (_isProcessing)
|
||||||
|
{
|
||||||
|
<MudProgressCircular Size="Size.Small" Indeterminate="true" Class="me-2" />
|
||||||
|
<span>در حال انتقال به درگاه...</span>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span>پرداخت با کارت بانکی</span>
|
||||||
|
}
|
||||||
|
</MudButton>
|
||||||
|
</MudStack>
|
||||||
|
</MudPaper>
|
||||||
|
|
||||||
|
<MudExpansionPanels Elevation="1" Class="rounded-lg">
|
||||||
|
<MudExpansionPanel Text="قوانین شارژ کیف پول اصلی" MaxHeight="500" IsInitiallyExpanded="false">
|
||||||
|
<MudList T="string" Dense="true">
|
||||||
|
<MudListItem T="string" Icon="@Icons.Material.Filled.CheckCircle" IconColor="Color.Success">
|
||||||
|
موجودی اصلی برای خرید از فروشگاه عادی و پرداخت سفارش قابل استفاده است
|
||||||
|
</MudListItem>
|
||||||
|
<MudListItem T="string" Icon="@Icons.Material.Filled.CheckCircle" IconColor="Color.Success">
|
||||||
|
حداقل مبلغ شارژ: ۱۰,۰۰۰ تومان
|
||||||
|
</MudListItem>
|
||||||
|
<MudListItem T="string" Icon="@Icons.Material.Filled.CheckCircle" IconColor="Color.Success">
|
||||||
|
مبلغ واریزی بدون ضریب به موجودی اضافه میشود (۱:۱)
|
||||||
|
</MudListItem>
|
||||||
|
<MudListItem T="string" Icon="@Icons.Material.Filled.Info" IconColor="Color.Info">
|
||||||
|
پرداخت از طریق درگاه بانکی انجام میشود
|
||||||
|
</MudListItem>
|
||||||
|
</MudList>
|
||||||
|
</MudExpansionPanel>
|
||||||
|
</MudExpansionPanels>
|
||||||
|
}
|
||||||
|
</MudStack>
|
||||||
|
</MudContainer>
|
||||||
@@ -0,0 +1,199 @@
|
|||||||
|
using FrontOffice.Main.Utilities;
|
||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using Microsoft.JSInterop;
|
||||||
|
using MudBlazor;
|
||||||
|
|
||||||
|
namespace FrontOffice.Main.Pages.Profile;
|
||||||
|
|
||||||
|
public partial class ChargeCreditWallet : ComponentBase
|
||||||
|
{
|
||||||
|
[Inject] private AuthService AuthService { get; set; } = default!;
|
||||||
|
|
||||||
|
private bool _isProcessing;
|
||||||
|
private long _chargeAmount;
|
||||||
|
private string? _paymentResult;
|
||||||
|
private string? _pendingReturnUrl;
|
||||||
|
private bool _isClubMemberActive;
|
||||||
|
private bool _hasPurchasedPackage;
|
||||||
|
private bool _authLoaded;
|
||||||
|
private bool _redirectingToMagic;
|
||||||
|
private bool _allowNormalDespiteMagic;
|
||||||
|
private bool _needsMagicCeilingConsent;
|
||||||
|
|
||||||
|
private readonly long[] _presetAmounts = { 500_000, 1_000_000, 5_000_000, 10_000_000, 20_000_000, 50_000_000 };
|
||||||
|
|
||||||
|
[SupplyParameterFromQuery(Name = "payment")]
|
||||||
|
public string? PaymentQueryParam { get; set; }
|
||||||
|
|
||||||
|
[SupplyParameterFromQuery(Name = "amount")]
|
||||||
|
public long? AmountQueryParam { get; set; }
|
||||||
|
|
||||||
|
[SupplyParameterFromQuery(Name = "returnUrl")]
|
||||||
|
public string? ReturnUrlQueryParam { get; set; }
|
||||||
|
|
||||||
|
private bool ShowChargeForm =>
|
||||||
|
_authLoaded
|
||||||
|
&& _isClubMemberActive
|
||||||
|
&& !_redirectingToMagic
|
||||||
|
&& (!_needsMagicCeilingConsent || _allowNormalDespiteMagic);
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
_paymentResult = PaymentQueryParam;
|
||||||
|
|
||||||
|
if (AmountQueryParam is > 0)
|
||||||
|
_chargeAmount = CreditChargeNavigation.NormalizeChargeAmount(AmountQueryParam.Value);
|
||||||
|
|
||||||
|
if (CreditChargeNavigation.IsValidReturnUrl(ReturnUrlQueryParam))
|
||||||
|
_pendingReturnUrl = ReturnUrlQueryParam;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var userInfo = await AuthService.GetUserAuthInfo();
|
||||||
|
_isClubMemberActive = userInfo.IsClubMemberActive;
|
||||||
|
_hasPurchasedPackage = userInfo.HasPurchasedPackage;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
_isClubMemberActive = false;
|
||||||
|
_hasPurchasedPackage = false;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_authLoaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_isClubMemberActive)
|
||||||
|
await EvaluateMagicWalletGateAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
|
{
|
||||||
|
if (firstRender && !string.IsNullOrEmpty(_pendingReturnUrl) && _isClubMemberActive && !_redirectingToMagic)
|
||||||
|
await CreditChargeNavigation.SaveReturnUrlAsync(JSRuntime, _pendingReturnUrl);
|
||||||
|
|
||||||
|
await base.OnAfterRenderAsync(firstRender);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AcceptMagicCeilingConsent()
|
||||||
|
{
|
||||||
|
_allowNormalDespiteMagic = true;
|
||||||
|
_redirectingToMagic = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DeclineMagicCeilingConsent()
|
||||||
|
{
|
||||||
|
_redirectingToMagic = true;
|
||||||
|
Navigation.NavigateTo(RouteConstants.Profile.MagicWallet);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task StartCreditCharge()
|
||||||
|
{
|
||||||
|
if (!_isClubMemberActive)
|
||||||
|
{
|
||||||
|
Snackbar.Add(
|
||||||
|
"برای شارژ کیف پول اصلی ابتدا باید پکیج را خریداری کرده و قرارداد باشگاه مشتریان را امضا کنید.",
|
||||||
|
Severity.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await EvaluateMagicWalletGateAsync())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (_needsMagicCeilingConsent && !_allowNormalDespiteMagic)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (_chargeAmount <= 0 || _isProcessing) return;
|
||||||
|
|
||||||
|
_isProcessing = true;
|
||||||
|
StateHasChanged();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(_pendingReturnUrl))
|
||||||
|
await CreditChargeNavigation.SaveReturnUrlAsync(JSRuntime, _pendingReturnUrl);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var storedReturnUrl = await CreditChargeNavigation.GetReturnUrlAsync(JSRuntime);
|
||||||
|
if (CreditChargeNavigation.IsValidReturnUrl(storedReturnUrl))
|
||||||
|
_pendingReturnUrl = storedReturnUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
var (success, gatewayUrl, error) = await WalletService.InitiateCreditChargeAsync(_chargeAmount);
|
||||||
|
|
||||||
|
if (success && !string.IsNullOrEmpty(gatewayUrl))
|
||||||
|
{
|
||||||
|
Navigation.NavigateTo(gatewayUrl, forceLoad: true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Snackbar.Add(error ?? "خطا در ایجاد درخواست پرداخت", Severity.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Snackbar.Add($"خطا: {ex.Message}", Severity.Error);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_isProcessing = false;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Magic با ظرفیت باقیمانده → ریدایرکت به MagicWallet.
|
||||||
|
/// Magic با سقف پر → نمایش تأیید روی صفحه برای شارژ ۱:۱.
|
||||||
|
/// true یعنی مسیر شارژ فعلی باید متوقف شود.
|
||||||
|
/// </summary>
|
||||||
|
private async Task<bool> EvaluateMagicWalletGateAsync()
|
||||||
|
{
|
||||||
|
if (_allowNormalDespiteMagic)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var status = await WalletService.GetMagicWalletStatusAsync();
|
||||||
|
if (status.WalletMode != 1)
|
||||||
|
{
|
||||||
|
_needsMagicCeilingConsent = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status.MagicRemainingDeposit > 0)
|
||||||
|
{
|
||||||
|
var multiplier = status.MagicMultiplier > 0 ? status.MagicMultiplier : 2.5;
|
||||||
|
var multiplierText = multiplier.ToString("0.#");
|
||||||
|
|
||||||
|
_redirectingToMagic = true;
|
||||||
|
_needsMagicCeilingConsent = false;
|
||||||
|
Snackbar.Add(
|
||||||
|
$"کیف پول شما در حالت جادویی است. شارژ از این صفحه موجودی را چندبرابر نمیکند. برای دریافت ضریب جادویی (×{multiplierText}) به صفحه شارژ کیف پول جادویی هدایت میشوید.",
|
||||||
|
Severity.Info);
|
||||||
|
|
||||||
|
Navigation.NavigateTo(RouteConstants.Profile.MagicWallet);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
_needsMagicCeilingConsent = true;
|
||||||
|
return !_allowNormalDespiteMagic;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// در صورت خطا در خواندن وضعیت، مانع شارژ عادی نشو
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ContinueShoppingAsync()
|
||||||
|
{
|
||||||
|
var returnUrl = await CreditChargeNavigation.GetReturnUrlAsync(JSRuntime);
|
||||||
|
await CreditChargeNavigation.ClearReturnUrlAsync(JSRuntime);
|
||||||
|
|
||||||
|
if (CreditChargeNavigation.IsValidReturnUrl(returnUrl))
|
||||||
|
Navigation.NavigateTo(returnUrl!);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string FormatToman(long toman)
|
||||||
|
=> string.Format("{0:N0} تومان", toman);
|
||||||
|
}
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
@attribute [Route(RouteConstants.Profile.ChargeDiscountWallet)]
|
||||||
|
@attribute [Authorize]
|
||||||
|
|
||||||
|
<PageTitle>شارژ کیفپول اعتباری</PageTitle>
|
||||||
|
|
||||||
|
<MudContainer MaxWidth="MaxWidth.Medium" Class="py-6">
|
||||||
|
<MudStack Spacing="3">
|
||||||
|
<PageHeader Title="💳 شارژ کیفپول اعتباری" BackHref="@RouteConstants.Profile.Wallet" />
|
||||||
|
|
||||||
|
@* ─── نتیجه پرداخت (اگه از callback برگشته) ─── *@
|
||||||
|
@if (_paymentResult != null)
|
||||||
|
{
|
||||||
|
<MudAlert Severity="@(_paymentResult == "success" ? MudBlazor.Severity.Success : MudBlazor.Severity.Error)"
|
||||||
|
Class="rounded-lg" Variant="Variant.Filled">
|
||||||
|
@if (_paymentResult == "success")
|
||||||
|
{
|
||||||
|
<span>شارژ اعتباری با موفقیت انجام شد! ✅</span>
|
||||||
|
}
|
||||||
|
else if (_paymentResult == "cancelled")
|
||||||
|
{
|
||||||
|
<span>پرداخت توسط شما لغو شد.</span>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span>پرداخت ناموفق بود. لطفاً دوباره تلاش کنید.</span>
|
||||||
|
}
|
||||||
|
</MudAlert>
|
||||||
|
}
|
||||||
|
|
||||||
|
@* ─── فرم شارژ ─── *@
|
||||||
|
<MudPaper Elevation="2" Class="pa-5 rounded-lg">
|
||||||
|
<MudText Typo="Typo.h6" Class="mb-3">
|
||||||
|
<MudIcon Icon="@Icons.Material.Filled.CreditCard" Class="ml-1" />
|
||||||
|
شارژ موجودی اعتباری
|
||||||
|
</MudText>
|
||||||
|
|
||||||
|
<MudAlert Severity="MudBlazor.Severity.Info" Dense="true" Class="mb-3" Icon="@Icons.Material.Filled.Info">
|
||||||
|
مبلغ وارد شده (به تومان) از طریق درگاه پرداخت به موجودی اعتباری شما اضافه میشود.
|
||||||
|
از موجودی اعتباری فقط برای خرید از فروشگاه اعتباری میتوانید استفاده کنید.
|
||||||
|
</MudAlert>
|
||||||
|
|
||||||
|
<MudStack Spacing="2">
|
||||||
|
<MudNumericField @bind-Value="_chargeAmount"
|
||||||
|
Label="مبلغ (تومان)"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Min="10_000"
|
||||||
|
Max="1_000_000_000"
|
||||||
|
Format="N0"
|
||||||
|
Adornment="Adornment.Start"
|
||||||
|
AdornmentIcon="@Icons.Material.Filled.Payments"
|
||||||
|
HelperText="حداقل ۱۰,۰۰۰ و حداکثر ۱,۰۰۰,۰۰۰,۰۰۰ تومان" />
|
||||||
|
|
||||||
|
@* دکمههای مبلغ سریع *@
|
||||||
|
<MudStack Row="true" Spacing="1" Class="flex-wrap">
|
||||||
|
@foreach (var preset in _presetAmounts)
|
||||||
|
{
|
||||||
|
<MudButton Variant="Variant.Outlined" Size="Size.Small"
|
||||||
|
Color="@(_chargeAmount == preset ? Color.Primary : Color.Default)"
|
||||||
|
OnClick="() => _chargeAmount = preset"
|
||||||
|
Class="rounded-pill">
|
||||||
|
@FormatToman(preset)
|
||||||
|
</MudButton>
|
||||||
|
}
|
||||||
|
</MudStack>
|
||||||
|
|
||||||
|
@if (_chargeAmount > 0)
|
||||||
|
{
|
||||||
|
<MudPaper Outlined="true" Class="pa-3 rounded-lg" Style="background: #f0fdf4;">
|
||||||
|
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||||||
|
<MudText Typo="Typo.body2">مبلغ پرداخت:</MudText>
|
||||||
|
<MudText Typo="Typo.body2"><strong>@FormatToman(_chargeAmount)</strong></MudText>
|
||||||
|
</MudStack>
|
||||||
|
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||||||
|
<MudText Typo="Typo.body2">اعتبار دریافتی:</MudText>
|
||||||
|
<MudText Typo="Typo.body2" Color="Color.Success"><strong>@FormatToman(_chargeAmount)</strong></MudText>
|
||||||
|
</MudStack>
|
||||||
|
</MudPaper>
|
||||||
|
}
|
||||||
|
|
||||||
|
<MudButton Variant="Variant.Filled"
|
||||||
|
Color="Color.Primary"
|
||||||
|
Size="Size.Large"
|
||||||
|
FullWidth="true"
|
||||||
|
Disabled="@(_isProcessing || _chargeAmount < 10_000)"
|
||||||
|
OnClick="StartDiscountCharge"
|
||||||
|
StartIcon="@Icons.Material.Filled.Payment"
|
||||||
|
Class="rounded-lg mt-2">
|
||||||
|
@if (_isProcessing)
|
||||||
|
{
|
||||||
|
<MudProgressCircular Size="Size.Small" Indeterminate="true" Class="me-2" />
|
||||||
|
<span>در حال انتقال به درگاه...</span>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span>پرداخت با کارت بانکی</span>
|
||||||
|
}
|
||||||
|
</MudButton>
|
||||||
|
</MudStack>
|
||||||
|
</MudPaper>
|
||||||
|
|
||||||
|
@* ─── قوانین ─── *@
|
||||||
|
<MudExpansionPanels Elevation="1" Class="rounded-lg">
|
||||||
|
<MudExpansionPanel Text="قوانین شارژ اعتباری" MaxHeight="500" IsInitiallyExpanded="false">
|
||||||
|
<MudList T="string" Dense="true">
|
||||||
|
<MudListItem T="string" Icon="@Icons.Material.Filled.CheckCircle" IconColor="Color.Success">
|
||||||
|
موجودی اعتباری فقط قابل استفاده در فروشگاه اعتباری است
|
||||||
|
</MudListItem>
|
||||||
|
<MudListItem T="string" Icon="@Icons.Material.Filled.CheckCircle" IconColor="Color.Success">
|
||||||
|
حداقل مبلغ شارژ: ۱۰,۰۰۰ تومان
|
||||||
|
</MudListItem>
|
||||||
|
<MudListItem T="string" Icon="@Icons.Material.Filled.CheckCircle" IconColor="Color.Success">
|
||||||
|
مبلغ واریزی بدون ضریب به موجودی اضافه میشود (۱:۱)
|
||||||
|
</MudListItem>
|
||||||
|
<MudListItem T="string" Icon="@Icons.Material.Filled.Info" IconColor="Color.Info">
|
||||||
|
پرداخت از طریق درگاه بانکی انجام میشود
|
||||||
|
</MudListItem>
|
||||||
|
</MudList>
|
||||||
|
</MudExpansionPanel>
|
||||||
|
</MudExpansionPanels>
|
||||||
|
</MudStack>
|
||||||
|
</MudContainer>
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
using FrontOffice.Main.Utilities;
|
||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using MudBlazor;
|
||||||
|
|
||||||
|
namespace FrontOffice.Main.Pages.Profile;
|
||||||
|
|
||||||
|
public partial class ChargeDiscountWallet : ComponentBase
|
||||||
|
{
|
||||||
|
private bool _isProcessing;
|
||||||
|
private long _chargeAmount;
|
||||||
|
private string? _paymentResult;
|
||||||
|
|
||||||
|
private readonly long[] _presetAmounts = { 500_000, 1_000_000, 5_000_000, 10_000_000, 20_000_000, 50_000_000 };
|
||||||
|
|
||||||
|
[SupplyParameterFromQuery(Name = "payment")]
|
||||||
|
public string? PaymentQueryParam { get; set; }
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
_paymentResult = PaymentQueryParam;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task StartDiscountCharge()
|
||||||
|
{
|
||||||
|
if (_chargeAmount <= 0 || _isProcessing) return;
|
||||||
|
|
||||||
|
_isProcessing = true;
|
||||||
|
StateHasChanged();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// مبلغ به تومان — CMS خودش موقع ارسال به درگاه ×۱۰ میکنه
|
||||||
|
var (success, gatewayUrl, error) = await WalletService.InitiateDiscountChargeAsync(_chargeAmount);
|
||||||
|
|
||||||
|
if (success && !string.IsNullOrEmpty(gatewayUrl))
|
||||||
|
{
|
||||||
|
Navigation.NavigateTo(gatewayUrl, forceLoad: true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Snackbar.Add(error ?? "خطا در ایجاد درخواست پرداخت", Severity.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Snackbar.Add($"خطا: {ex.Message}", Severity.Error);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_isProcessing = false;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string FormatToman(long toman)
|
||||||
|
=> string.Format("{0:N0} تومان", toman);
|
||||||
|
}
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
|
|
||||||
<MudDialog >
|
@using CMSMicroservice.Protobuf.Protos.City
|
||||||
|
<MudDialog>
|
||||||
<TitleContent>
|
<TitleContent>
|
||||||
<MudText Typo="Typo.h4" Align="Align.Center">افزودن آدرس جدید</MudText>
|
<MudText Typo="Typo.h4" Align="Align.Center">افزودن آدرس جدید</MudText>
|
||||||
</TitleContent>
|
</TitleContent>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<MudForm @ref="_form" Model="_request" Validation="@(_validator.ValidateValue)">
|
<MudForm @ref="_form" Model="_request">
|
||||||
<MudStack Spacing="3">
|
<MudStack Spacing="3">
|
||||||
<MudTextField @bind-Value="_request.Title"
|
<MudTextField @bind-Value="_request.Title"
|
||||||
For="@(() => _request.Title)"
|
For="@(() => _request.Title)"
|
||||||
@@ -29,12 +30,12 @@
|
|||||||
Required="true"
|
Required="true"
|
||||||
RequiredError="کد پستی الزامی است." />
|
RequiredError="کد پستی الزامی است." />
|
||||||
|
|
||||||
<MudAutocomplete T="CityProto.GetAllCitiesByFilterResponseModel"
|
<MudAutocomplete T="object"
|
||||||
@bind-Value="_selectedCity"
|
@bind-Value="_selectedCity"
|
||||||
Label="شهر"
|
Label="شهر"
|
||||||
Variant="Variant.Outlined"
|
Variant="Variant.Outlined"
|
||||||
SearchFunc="SearchCities"
|
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"
|
Required="true"
|
||||||
RequiredError="شهر الزامی است."
|
RequiredError="شهر الزامی است."
|
||||||
Clearable="true"
|
Clearable="true"
|
||||||
@@ -49,8 +50,8 @@
|
|||||||
<ItemTemplate Context="city">
|
<ItemTemplate Context="city">
|
||||||
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
|
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
|
||||||
<MudIcon Icon="@Icons.Material.Filled.LocationCity" Size="Size.Small" />
|
<MudIcon Icon="@Icons.Material.Filled.LocationCity" Size="Size.Small" />
|
||||||
<MudText>@city.Native</MudText>
|
<MudText>@(((CMSMicroservice.Protobuf.Protos.City.CityDto)city).Native)</MudText>
|
||||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">(@city.StateName)</MudText>
|
<MudText Typo="Typo.caption" Class="mud-text-secondary">(@(((CMSMicroservice.Protobuf.Protos.City.CityDto)city).StateName))</MudText>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
</ItemTemplate>
|
</ItemTemplate>
|
||||||
<NoItemsTemplate>
|
<NoItemsTemplate>
|
||||||
|
|||||||
@@ -1,43 +1,42 @@
|
|||||||
using FrontOffice.BFF.UserAddress.Protobuf.Protos.UserAddress;
|
using CMSMicroservice.Protobuf.Protos.UserAddress;
|
||||||
using FrontOffice.BFF.UserAddress.Protobuf.Validator;
|
using CMSMicroservice.Protobuf.Protos.City;
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
using MudBlazor;
|
using MudBlazor;
|
||||||
using Severity = MudBlazor.Severity;
|
using Severity = MudBlazor.Severity;
|
||||||
using CityProto = FrontOffice.BFF.City.Protobuf;
|
|
||||||
|
|
||||||
namespace FrontOffice.Main.Pages.Profile.Components;
|
namespace FrontOffice.Main.Pages.Profile.Components;
|
||||||
|
|
||||||
|
public record CityModel(long Id, string Name);
|
||||||
|
|
||||||
public partial class AddAddressDialog : ComponentBase
|
public partial class AddAddressDialog : ComponentBase
|
||||||
{
|
{
|
||||||
[CascadingParameter] private IMudDialogInstance MudDialog { get; set; } = default!;
|
[CascadingParameter] private IMudDialogInstance MudDialog { get; set; } = default!;
|
||||||
[Inject] private UserAddressContract.UserAddressContractClient UserAddressContract { 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 MudForm? _form;
|
||||||
private readonly CreateNewUserAddressRequestValidator _validator = new();
|
|
||||||
private bool _isSaving;
|
private bool _isSaving;
|
||||||
private CreateNewUserAddressRequest _request = new();
|
private CreateCustomerAddressRequest _request = new();
|
||||||
private CityProto.GetAllCitiesByFilterResponseModel? _selectedCity;
|
private object? _selectedCity;
|
||||||
|
|
||||||
private async Task<IEnumerable<CityProto.GetAllCitiesByFilterResponseModel>> SearchCities(string value,
|
private async Task<IEnumerable<object>> SearchCities(string value, CancellationToken ct)
|
||||||
CancellationToken ct)
|
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(value) || value.Length < 2)
|
if (string.IsNullOrWhiteSpace(value) || value.Length < 2)
|
||||||
return Enumerable.Empty<CityProto.GetAllCitiesByFilterResponseModel>();
|
return Enumerable.Empty<object>();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var response = await CityContract.GetAllCitiesByFilterAsync(new CityProto.GetAllCitiesByFilterRequest
|
var response = await CityContract.GetAllCitiesByFilterAsync(new GetAllCitiesByFilterRequest
|
||||||
{
|
{
|
||||||
PaginationState = new CityProto.PaginationState { PageNumber = 1, PageSize = 20 },
|
PaginationState = new CMSMicroservice.Protobuf.Protos.City.PaginationState { PageNumber = 1, PageSize = 20 },
|
||||||
Filter = new CityProto.GetAllCitiesByFilterFilter { Name = value }
|
Filter = new GetAllCitiesByFilterFilter { Name = value }
|
||||||
}, cancellationToken: ct);
|
});
|
||||||
|
|
||||||
return response?.Models?.ToList() ?? new List<CityProto.GetAllCitiesByFilterResponseModel>();
|
return response?.Cities?.Cast<object>() ?? Enumerable.Empty<object>();
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
return Enumerable.Empty<CityProto.GetAllCitiesByFilterResponseModel>();
|
return Enumerable.Empty<object>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,11 +52,11 @@ public partial class AddAddressDialog : ComponentBase
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_request.CityId = _selectedCity.Id;
|
_request.CityId = (long)((dynamic)_selectedCity).Id;
|
||||||
_isSaving = true;
|
_isSaving = true;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var response = await UserAddressContract.CreateNewUserAddressAsync(_request);
|
var response = await UserAddressContract.CreateCustomerAddressAsync(_request);
|
||||||
Snackbar.Add("آدرس با موفقیت اضافه شد.", Severity.Success);
|
Snackbar.Add("آدرس با موفقیت اضافه شد.", Severity.Success);
|
||||||
MudDialog.Close(DialogResult.Ok(true));
|
MudDialog.Close(DialogResult.Ok(true));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
|
@using CMSMicroservice.Protobuf.Protos.City
|
||||||
<MudDialog>
|
<MudDialog>
|
||||||
<TitleContent>
|
<TitleContent>
|
||||||
<MudText Typo="Typo.h4" Align="Align.Center">ویرایش آدرس</MudText>
|
<MudText Typo="Typo.h4" Align="Align.Center">ویرایش آدرس</MudText>
|
||||||
</TitleContent>
|
</TitleContent>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<MudForm @ref="_form" Model="_request" Validation="@(_validator.ValidateValue)">
|
<MudForm @ref="_form" Model="_request">
|
||||||
<MudStack Spacing="3">
|
<MudStack Spacing="3">
|
||||||
<MudTextField @bind-Value="_request.Title"
|
<MudTextField @bind-Value="_request.Title"
|
||||||
For="@(() => _request.Title)"
|
For="@(() => _request.Title)"
|
||||||
@@ -28,12 +29,12 @@
|
|||||||
Required="true"
|
Required="true"
|
||||||
RequiredError="کد پستی الزامی است." />
|
RequiredError="کد پستی الزامی است." />
|
||||||
|
|
||||||
<MudAutocomplete T="CityProto.GetAllCitiesByFilterResponseModel"
|
<MudAutocomplete T="object"
|
||||||
@bind-Value="_selectedCity"
|
@bind-Value="_selectedCity"
|
||||||
Label="شهر"
|
Label="شهر"
|
||||||
Variant="Variant.Outlined"
|
Variant="Variant.Outlined"
|
||||||
SearchFunc="SearchCities"
|
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"
|
Required="true"
|
||||||
RequiredError="شهر الزامی است."
|
RequiredError="شهر الزامی است."
|
||||||
Clearable="true"
|
Clearable="true"
|
||||||
@@ -48,8 +49,8 @@
|
|||||||
<ItemTemplate Context="city">
|
<ItemTemplate Context="city">
|
||||||
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
|
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
|
||||||
<MudIcon Icon="@Icons.Material.Filled.LocationCity" Size="Size.Small" />
|
<MudIcon Icon="@Icons.Material.Filled.LocationCity" Size="Size.Small" />
|
||||||
<MudText>@city.Native</MudText>
|
<MudText>@(((CMSMicroservice.Protobuf.Protos.City.CityDto)city).Native)</MudText>
|
||||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">(@city.StateName)</MudText>
|
<MudText Typo="Typo.caption" Class="mud-text-secondary">(@(((CMSMicroservice.Protobuf.Protos.City.CityDto)city).StateName))</MudText>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
</ItemTemplate>
|
</ItemTemplate>
|
||||||
<NoItemsTemplate>
|
<NoItemsTemplate>
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
using FrontOffice.BFF.UserAddress.Protobuf.Protos.UserAddress;
|
using CMSMicroservice.Protobuf.Protos.UserAddress;
|
||||||
using FrontOffice.BFF.UserAddress.Protobuf.Validator;
|
using CMSMicroservice.Protobuf.Protos.City;
|
||||||
using Mapster;
|
using Mapster;
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
using MudBlazor;
|
using MudBlazor;
|
||||||
using Severity = MudBlazor.Severity;
|
using Severity = MudBlazor.Severity;
|
||||||
using CityProto = FrontOffice.BFF.City.Protobuf;
|
|
||||||
|
|
||||||
namespace FrontOffice.Main.Pages.Profile.Components;
|
namespace FrontOffice.Main.Pages.Profile.Components;
|
||||||
|
|
||||||
@@ -12,22 +11,21 @@ public partial class EditAddressDialog : ComponentBase
|
|||||||
{
|
{
|
||||||
[CascadingParameter] private IMudDialogInstance MudDialog { get; set; } = default!;
|
[CascadingParameter] private IMudDialogInstance MudDialog { get; set; } = default!;
|
||||||
[Inject] private UserAddressContract.UserAddressContractClient UserAddressContract { 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 MudForm? _form;
|
||||||
private readonly UpdateUserAddressRequestValidator _validator = new();
|
|
||||||
private bool _isSaving;
|
private bool _isSaving;
|
||||||
private UpdateUserAddressRequest _request = new();
|
private UpdateCustomerAddressRequest _request = new();
|
||||||
private CityProto.GetAllCitiesByFilterResponseModel? _selectedCity;
|
private object? _selectedCity;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
await base.OnInitializedAsync();
|
await base.OnInitializedAsync();
|
||||||
if (Model != null)
|
if (Model != null)
|
||||||
{
|
{
|
||||||
_request = Model.Adapt<UpdateUserAddressRequest>();
|
_request = Model.Adapt<UpdateCustomerAddressRequest>();
|
||||||
|
|
||||||
// Load selected city if CityId exists
|
// Load selected city if CityId exists
|
||||||
if (Model.CityId > 0)
|
if (Model.CityId > 0)
|
||||||
@@ -41,13 +39,13 @@ public partial class EditAddressDialog : ComponentBase
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var response = await CityContract.GetAllCitiesByFilterAsync(new CityProto.GetAllCitiesByFilterRequest
|
var response = await CityContract.GetAllCitiesByFilterAsync(new GetAllCitiesByFilterRequest
|
||||||
{
|
{
|
||||||
PaginationState = new CityProto.PaginationState { PageNumber = 1, PageSize = 1 },
|
PaginationState = new CMSMicroservice.Protobuf.Protos.City.PaginationState { PageNumber = 1, PageSize = 1 },
|
||||||
Filter = new CityProto.GetAllCitiesByFilterFilter { Id = cityId }
|
Filter = new GetAllCitiesByFilterFilter { Id = cityId }
|
||||||
});
|
});
|
||||||
|
|
||||||
_selectedCity = response?.Models?.FirstOrDefault();
|
_selectedCity = response?.Cities?.FirstOrDefault();
|
||||||
}
|
}
|
||||||
catch
|
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)
|
if (string.IsNullOrWhiteSpace(value) || value.Length < 2)
|
||||||
return Enumerable.Empty<CityProto.GetAllCitiesByFilterResponseModel>();
|
return Enumerable.Empty<object>();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var response = await CityContract.GetAllCitiesByFilterAsync(new CityProto.GetAllCitiesByFilterRequest
|
var response = await CityContract.GetAllCitiesByFilterAsync(new GetAllCitiesByFilterRequest
|
||||||
{
|
{
|
||||||
PaginationState = new CityProto.PaginationState { PageNumber = 1, PageSize = 20 },
|
PaginationState = new CMSMicroservice.Protobuf.Protos.City.PaginationState { PageNumber = 1, PageSize = 20 },
|
||||||
Filter = new CityProto.GetAllCitiesByFilterFilter { Native = value }
|
Filter = new GetAllCitiesByFilterFilter { Name = value }
|
||||||
}, cancellationToken: ct);
|
});
|
||||||
|
|
||||||
return response?.Models?.ToList() ?? new List<CityProto.GetAllCitiesByFilterResponseModel>();
|
return response?.Cities?.Cast<object>() ?? Enumerable.Empty<object>();
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
return Enumerable.Empty<CityProto.GetAllCitiesByFilterResponseModel>();
|
return Enumerable.Empty<object>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,11 +86,11 @@ public partial class EditAddressDialog : ComponentBase
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_request.CityId = _selectedCity.Id;
|
_request.CityId = (long)((dynamic)_selectedCity).Id;
|
||||||
_isSaving = true;
|
_isSaving = true;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await UserAddressContract.UpdateUserAddressAsync(_request);
|
await UserAddressContract.UpdateCustomerAddressAsync(_request);
|
||||||
Snackbar.Add("آدرس با موفقیت ویرایش شد.", Severity.Success);
|
Snackbar.Add("آدرس با موفقیت ویرایش شد.", Severity.Success);
|
||||||
MudDialog.Close(DialogResult.Ok(true));
|
MudDialog.Close(DialogResult.Ok(true));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,12 +7,12 @@
|
|||||||
|
|
||||||
<MudSelect T="int" @bind-Value="_selectedDepth" Label="عمق" Variant="Variant.Outlined"
|
<MudSelect T="int" @bind-Value="_selectedDepth" Label="عمق" Variant="Variant.Outlined"
|
||||||
Dense="true" Margin="Margin.Dense" Style="width: 90px; min-width: 90px;">
|
Dense="true" Margin="Margin.Dense" Style="width: 90px; min-width: 90px;">
|
||||||
<MudSelectItem Value="2">2 سطح</MudSelectItem>
|
<MudSelectItem T="int" Value="2">2 سطح</MudSelectItem>
|
||||||
<MudSelectItem Value="3">3 سطح</MudSelectItem>
|
<MudSelectItem T="int" Value="3">3 سطح</MudSelectItem>
|
||||||
<MudSelectItem Value="4">4 سطح</MudSelectItem>
|
<MudSelectItem T="int" Value="4">4 سطح</MudSelectItem>
|
||||||
<MudSelectItem Value="5">5 سطح</MudSelectItem>
|
<MudSelectItem T="int" Value="5">5 سطح</MudSelectItem>
|
||||||
<MudSelectItem Value="6">6 سطح</MudSelectItem>
|
<MudSelectItem T="int" Value="6">6 سطح</MudSelectItem>
|
||||||
<MudSelectItem Value="15">همه</MudSelectItem>
|
<MudSelectItem T="int" Value="15">همه</MudSelectItem>
|
||||||
</MudSelect>
|
</MudSelect>
|
||||||
|
|
||||||
<MudButtonGroup Variant="Variant.Outlined" Size="Size.Small" OverrideStyles="false">
|
<MudButtonGroup Variant="Variant.Outlined" Size="Size.Small" OverrideStyles="false">
|
||||||
@@ -61,11 +61,11 @@
|
|||||||
<span class="stat-value">@_statistics.TotalMembers</span>
|
<span class="stat-value">@_statistics.TotalMembers</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-item">
|
<div class="stat-item">
|
||||||
<span class="stat-label">پای چپ:</span>
|
<span class="stat-label">سازمان چپ:</span>
|
||||||
<span class="stat-value left">@_statistics.LeftLegCount</span>
|
<span class="stat-value left">@_statistics.LeftLegCount</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-item">
|
<div class="stat-item">
|
||||||
<span class="stat-label">پای راست:</span>
|
<span class="stat-label">سازمان راست:</span>
|
||||||
<span class="stat-value right">@_statistics.RightLegCount</span>
|
<span class="stat-value right">@_statistics.RightLegCount</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-item">
|
<div class="stat-item">
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using FrontOffice.Main.Utilities;
|
using FrontOffice.Main.Utilities;
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
using Microsoft.JSInterop;
|
using Microsoft.JSInterop;
|
||||||
|
|
||||||
@@ -95,7 +95,23 @@ public partial class OrganizationChart : IAsyncDisposable
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
_dotNetHelper = DotNetObjectReference.Create(this);
|
_dotNetHelper = DotNetObjectReference.Create(this);
|
||||||
var flatData = _networkTree.ToFlatArray();
|
// Explicit camelCase mapping (same pattern as BackOffice admin chart)
|
||||||
|
var flatData = _networkTree.ToFlatArray().Select(n => new
|
||||||
|
{
|
||||||
|
id = n.Id,
|
||||||
|
parentId = n.ParentId,
|
||||||
|
fullName = n.FullName,
|
||||||
|
mobile = n.Mobile,
|
||||||
|
avatar = n.Avatar,
|
||||||
|
position = n.Position,
|
||||||
|
level = n.Level,
|
||||||
|
isActive = n.IsActive,
|
||||||
|
isClubActive = n.IsClubActive,
|
||||||
|
activationWeekNumber = n.ActivationWeekNumber,
|
||||||
|
joinedAt = n.JoinedAt,
|
||||||
|
referralCode = n.ReferralCode,
|
||||||
|
packageName = n.PackageName ?? string.Empty
|
||||||
|
}).ToArray();
|
||||||
|
|
||||||
await JSRuntime.InvokeVoidAsync("OrgChart.init", "org-chart-container", flatData, _dotNetHelper);
|
await JSRuntime.InvokeVoidAsync("OrgChart.init", "org-chart-container", flatData, _dotNetHelper);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,9 +9,9 @@
|
|||||||
@if (!string.IsNullOrWhiteSpace(node.Avatar))
|
@if (!string.IsNullOrWhiteSpace(node.Avatar))
|
||||||
{
|
{
|
||||||
<MudAvatar Size="@GetAvatarSize(Level)">
|
<MudAvatar Size="@GetAvatarSize(Level)">
|
||||||
<MudImage ObjectFit="ObjectFit.Cover"
|
<AppImage Path="@node.Avatar"
|
||||||
ObjectPosition="ObjectPosition.Center"
|
ObjectFit="ObjectFit.Cover"
|
||||||
Src="@node.Avatar" />
|
ObjectPosition="ObjectPosition.Center" />
|
||||||
</MudAvatar>
|
</MudAvatar>
|
||||||
}
|
}
|
||||||
else
|
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,301 +5,194 @@
|
|||||||
|
|
||||||
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
||||||
<MudGrid Spacing="4">
|
<MudGrid Spacing="4">
|
||||||
<!-- Profile Header -->
|
|
||||||
|
@* ═══════════════════════════════════════════════
|
||||||
|
1. PROFILE HEADER — Modern gradient card
|
||||||
|
═══════════════════════════════════════════════ *@
|
||||||
<MudItem xs="12">
|
<MudItem xs="12">
|
||||||
<MudPaper Elevation="4" Class="pa-6">
|
<MudPaper Elevation="0" Class="dash-header-card pa-5 pa-md-6 rounded-xl">
|
||||||
<MudStack Spacing="4">
|
<MudStack Row="true" Spacing="3" AlignItems="AlignItems.Center" Class="flex-wrap">
|
||||||
<MudStack Row="true" Spacing="3" AlignItems="AlignItems.Center">
|
<MudAvatar Size="Size.Large" Class="dash-avatar">
|
||||||
<MudStack Row="true" Spacing="3" AlignItems="AlignItems.Center">
|
<MudIcon Icon="@Icons.Material.Filled.Person" Size="Size.Large" />
|
||||||
<MudAvatar Size="Size.Large" Color="Color.Primary">
|
</MudAvatar>
|
||||||
<MudIcon Icon="@Icons.Material.Filled.Person" Size="Size.Large" />
|
<MudStack Spacing="0" Class="flex-grow-1">
|
||||||
</MudAvatar>
|
<MudText Typo="Typo.h5" Class="dash-hero-name">
|
||||||
<div>
|
@($"{_userProfile.FirstName} {_userProfile.LastName}")
|
||||||
<MudText Typo="Typo.h5">@($"{_userProfile.FirstName} {_userProfile.LastName}")</MudText>
|
</MudText>
|
||||||
<MudText Typo="Typo.body2" Class="mud-text-secondary">@(_userProfile.Mobile)</MudText>
|
<MudText Typo="Typo.body2" Class="dash-hero-sub">
|
||||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">عضو از @(_userProfile.MobileVerifiedAt?.ToDateTime().MiladiToJalali())</MudText>
|
@(_userProfile.Mobile)
|
||||||
</div>
|
</MudText>
|
||||||
</MudStack>
|
<MudText Typo="Typo.caption" Class="dash-hero-hint">
|
||||||
<MudSpacer/>
|
عضو از @(_userProfile.MobileVerifiedAt?.ToDateTime().MiladiToJalali())
|
||||||
|
</MudText>
|
||||||
<MudButton Variant="Variant.Outlined" Href="https://dayadiamond.ir/" >سامانه دایا</MudButton>
|
|
||||||
</MudStack>
|
</MudStack>
|
||||||
|
<MudButton Variant="Variant.Filled"
|
||||||
|
Class="rounded-pill dash-hero-btn"
|
||||||
<!-- Referral Code Section -->
|
Href="https://dayadiamond.ir/"
|
||||||
<MudDivider />
|
StartIcon="@Icons.Material.Filled.Diamond">
|
||||||
|
سامانه دایا
|
||||||
@if (CanShowReferralLink)
|
</MudButton>
|
||||||
{
|
|
||||||
<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>
|
|
||||||
}
|
|
||||||
</MudStack>
|
</MudStack>
|
||||||
</MudPaper>
|
</MudPaper>
|
||||||
</MudItem>
|
</MudItem>
|
||||||
|
|
||||||
<!-- Wallet Highlight Row -->
|
@* ═══════════════════════════════════════════════
|
||||||
|
2. WALLET STRIP — Compact inline stats
|
||||||
|
═══════════════════════════════════════════════ *@
|
||||||
<MudItem xs="12">
|
<MudItem xs="12">
|
||||||
<MudPaper Elevation="3" Class="pa-4 rounded-lg gradient-border">
|
<MudPaper Elevation="1" Class="pa-2 pa-md-4 rounded-xl">
|
||||||
<MudGrid Spacing="2" AlignItems="AlignItems.Center">
|
<MudStack Row="true" Justify="Justify.SpaceAround" AlignItems="AlignItems.Center" Class="flex-wrap wallet-strip">
|
||||||
<MudItem xs="12" sm="6" md="4">
|
<div class="wallet-strip-item">
|
||||||
<MudStack>
|
<div class="wallet-strip-label">
|
||||||
<MudText Typo="Typo.subtitle2" Class="mud-text-secondary">موجودی اعتباری</MudText>
|
<MudIcon Icon="@Icons.Material.Outlined.CreditCard" Size="Size.Small" Color="Color.Primary" />
|
||||||
<MudText Typo="Typo.h5" Color="Color.Primary">@_walletCredit</MudText>
|
<MudText Typo="Typo.caption" Class="mud-text-secondary">اصلی</MudText>
|
||||||
</MudStack>
|
</div>
|
||||||
</MudItem>
|
<MudText Typo="Typo.subtitle2" Color="Color.Primary">@_walletCredit</MudText>
|
||||||
<MudItem xs="12" sm="6" md="4">
|
</div>
|
||||||
<MudStack>
|
<MudDivider Vertical="true" FlexItem="true" Class="d-none d-sm-flex" />
|
||||||
<MudText Typo="Typo.subtitle2" Class="mud-text-secondary">موجودی تخفیف باشگاه</MudText>
|
<div class="wallet-strip-item">
|
||||||
<MudText Typo="Typo.h5" Color="Color.Primary">@_walletDiscount</MudText>
|
<div class="wallet-strip-label">
|
||||||
</MudStack>
|
<MudIcon Icon="@Icons.Material.Outlined.Discount" Size="Size.Small" Color="Color.Success" />
|
||||||
</MudItem>
|
<MudText Typo="Typo.caption" Class="mud-text-secondary">کیف اعتباری</MudText>
|
||||||
<MudItem xs="12" sm="6" md="4">
|
</div>
|
||||||
<MudStack>
|
<MudText Typo="Typo.subtitle2" Color="Color.Success">@_walletDiscount</MudText>
|
||||||
<MudText Typo="Typo.subtitle2" Class="mud-text-secondary">موجودی پاداش تیمی</MudText>
|
</div>
|
||||||
<MudText Typo="Typo.h5" Color="Color.Primary">@_walletNetwork</MudText>
|
<MudDivider Vertical="true" FlexItem="true" Class="d-none d-sm-flex" />
|
||||||
</MudStack>
|
<div class="wallet-strip-item">
|
||||||
</MudItem>
|
<div class="wallet-strip-label">
|
||||||
<MudItem xs="12" md="4" Class="d-flex justify-end">
|
<MudIcon Icon="@Icons.Material.Outlined.Groups" Size="Size.Small" Color="Color.Warning" />
|
||||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" StartIcon="@Icons.Material.Filled.AccountBalanceWallet" Href="@RouteConstants.Profile.Wallet">جزئیات کیف پول</MudButton>
|
<MudText Typo="Typo.caption" Class="mud-text-secondary">پاداش های دریافتی</MudText>
|
||||||
</MudItem>
|
</div>
|
||||||
</MudGrid>
|
<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>
|
</MudPaper>
|
||||||
</MudItem>
|
</MudItem>
|
||||||
|
|
||||||
<!-- Profile Content as tiles -->
|
@* ═══════════════════════════════════════════════
|
||||||
|
3. REFERRAL SECTION
|
||||||
|
═══════════════════════════════════════════════ *@
|
||||||
<MudItem xs="12">
|
<MudItem xs="12">
|
||||||
<MudPaper Elevation="4" Class="pa-4">
|
@if (CanShowReferralLink)
|
||||||
<MudGrid Spacing="3">
|
{
|
||||||
<MudItem xs="6" sm="6" md="3">
|
<MudPaper Elevation="1" Class="pa-5 rounded-xl">
|
||||||
<MudLink Href="@RouteConstants.Profile.Personal" Underline="Underline.None" Class="tile-link">
|
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2" Class="mb-3">
|
||||||
<MudCard Elevation="1" Class="rounded-lg profile-tile">
|
<MudIcon Icon="@Icons.Material.Filled.Share" Color="Color.Primary" />
|
||||||
<MudCardContent Class="d-flex flex-column align-center pa-4">
|
<MudText Typo="Typo.h6">کد دعوت شما</MudText>
|
||||||
<MudIcon Icon="@Icons.Material.Filled.Person" Size="Size.Large" Color="Color.Primary" />
|
<MudSpacer />
|
||||||
<MudText Typo="Typo.subtitle1" Class="mt-2">اطلاعات شخصی</MudText>
|
<MudButton Variant="Variant.Filled"
|
||||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">نمایش و ویرایش اطلاعات</MudText>
|
Color="Color.Primary"
|
||||||
</MudCardContent>
|
Size="Size.Small"
|
||||||
</MudCard>
|
Class="rounded-pill"
|
||||||
</MudLink>
|
StartIcon="@Icons.Material.Filled.Share"
|
||||||
</MudItem>
|
OnClick="ShareReferralCode">
|
||||||
<MudItem xs="6" sm="6" md="3">
|
اشتراکگذاری
|
||||||
<MudLink Href="@RouteConstants.Profile.Addresses" Underline="Underline.None" Class="tile-link">
|
</MudButton>
|
||||||
<MudCard Elevation="1" Class="rounded-lg profile-tile">
|
</MudStack>
|
||||||
<MudCardContent Class="d-flex flex-column align-center pa-4">
|
<MudText Typo="Typo.body2" Class="mud-text-secondary mb-3">
|
||||||
<MudIcon Icon="@Icons.Material.Filled.LocationOn" Size="Size.Large" Color="Color.Primary" />
|
این کد را با دوستان خود به اشتراک بگذارید تا از خریدهای آنها پاداش دریافت کنید.
|
||||||
<MudText Typo="Typo.subtitle1" Class="mt-2">آدرسها</MudText>
|
</MudText>
|
||||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">مدیریت آدرسهای شما</MudText>
|
<MudTextField @bind-Value="_userProfile.ReferralCode"
|
||||||
</MudCardContent>
|
Label="کد دعوت"
|
||||||
</MudCard>
|
Variant="Variant.Outlined"
|
||||||
</MudLink>
|
ReadOnly="true"
|
||||||
</MudItem>
|
Adornment="Adornment.End"
|
||||||
<MudItem xs="6" sm="6" md="3">
|
AdornmentIcon="@Icons.Material.Filled.ContentCopy"
|
||||||
<MudLink Href="@RouteConstants.Profile.Tree" Underline="Underline.None" Class="tile-link">
|
OnAdornmentClick="CopyReferralCode"
|
||||||
<MudCard Elevation="1" Class="rounded-lg profile-tile">
|
Class="rounded-lg" />
|
||||||
<MudCardContent Class="d-flex flex-column align-center pa-4">
|
@if (!string.IsNullOrWhiteSpace(_copyMessage))
|
||||||
<MudIcon Icon="@Icons.Material.Filled.AccountTree" Size="Size.Large" Color="Color.Primary" />
|
{
|
||||||
<MudText Typo="Typo.subtitle1" Class="mt-2">شجرهنامه</MudText>
|
<MudText Typo="Typo.caption" Color="Color.Success" Class="mt-2">@(_copyMessage)</MudText>
|
||||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">ساختار تیم</MudText>
|
}
|
||||||
</MudCardContent>
|
</MudPaper>
|
||||||
</MudCard>
|
}
|
||||||
</MudLink>
|
else
|
||||||
</MudItem>
|
{
|
||||||
<MudItem xs="6" sm="6" md="3">
|
<MudPaper Elevation="0" Class="pa-6 rounded-xl text-center" Style="border:2px dashed var(--mud-palette-primary); background:rgba(99,102,241,.04);">
|
||||||
<MudLink Href="@RouteConstants.Profile.Settings" Underline="Underline.None" Class="tile-link">
|
<MudIcon Icon="@Icons.Material.Filled.Lock" Size="Size.Large" Color="Color.Primary" Class="mb-2" Style="font-size:2.5rem;" />
|
||||||
<MudCard Elevation="1" Class="rounded-lg profile-tile">
|
<MudText Typo="Typo.h6" Color="Color.Primary" Class="mb-2">
|
||||||
<MudCardContent Class="d-flex flex-column align-center pa-4">
|
لینک دعوت شما هنوز فعال نشده است
|
||||||
<MudIcon Icon="@Icons.Material.Filled.Settings" Size="Size.Large" Color="Color.Primary" />
|
</MudText>
|
||||||
<MudText Typo="Typo.subtitle1" Class="mt-2">تنظیمات حساب</MudText>
|
<MudText Typo="Typo.body2" Class="mud-text-secondary mb-1" Style="max-width:560px; margin:0 auto;">
|
||||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">اعلانها و تنظیمات</MudText>
|
برای فعالسازی لینک دعوت، ابتدا یکی از <strong>پکیجها</strong> را تهیه کنید و سپس در <strong>باشگاه مشتریان</strong> عضو شوید.
|
||||||
</MudCardContent>
|
</MudText>
|
||||||
</MudCard>
|
<MudText Typo="Typo.body2" Class="mud-text-secondary mb-4" Style="max-width:560px; margin:0 auto;">
|
||||||
</MudLink>
|
از مزایای ویژه عضویت بهرهمند شده و با فعالیت در زمینه توسعه فروشگاهها پاداش دریافت کنید.
|
||||||
</MudItem>
|
</MudText>
|
||||||
<MudItem xs="6" sm="6" md="3">
|
<MudButton Variant="Variant.Filled"
|
||||||
<MudLink Href="@RouteConstants.Store.Products" Underline="Underline.None" Class="tile-link">
|
Color="Color.Primary"
|
||||||
<MudCard Elevation="1" Class="rounded-lg profile-tile">
|
Size="Size.Large"
|
||||||
<MudCardContent Class="d-flex flex-column align-center pa-4">
|
Class="rounded-pill"
|
||||||
<MudIcon Icon="@Icons.Material.Filled.Storefront" Size="Size.Large" Color="Color.Primary" />
|
StartIcon="@Icons.Material.Filled.ShoppingCart"
|
||||||
<MudText Typo="Typo.subtitle1" Class="mt-2">فروشگاه</MudText>
|
OnClick="OpenPurchaseOptions">
|
||||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">مشاهده و خرید محصولات</MudText>
|
تهیه پکیج
|
||||||
</MudCardContent>
|
</MudButton>
|
||||||
</MudCard>
|
</MudPaper>
|
||||||
</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>
|
|
||||||
</MudItem>
|
</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>
|
</MudGrid>
|
||||||
</MudContainer>
|
</MudContainer>
|
||||||
|
|
||||||
<!-- Purchase Options Bottom Sheet -->
|
|
||||||
<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">
|
|
||||||
پرداخت با کارت بانکی(بزودی)
|
|
||||||
</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">
|
|
||||||
تأمین اعتبار الماسی
|
|
||||||
</MudButton>
|
|
||||||
</MudStack>
|
|
||||||
</MudPaper>
|
|
||||||
|
|
||||||
<MudButton Variant="Variant.Text"
|
|
||||||
Color="Color.Default"
|
|
||||||
FullWidth="true"
|
|
||||||
OnClick="ClosePurchaseOptions">
|
|
||||||
بستن
|
|
||||||
</MudButton>
|
|
||||||
</MudStack>
|
|
||||||
</DialogContent>
|
|
||||||
</MudDialog>
|
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
using FluentValidation;
|
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.Pages.Profile.Components;
|
||||||
using FrontOffice.Main.Utilities;
|
using FrontOffice.Main.Utilities;
|
||||||
using Mapster;
|
using Mapster;
|
||||||
@@ -11,6 +7,11 @@ using Microsoft.AspNetCore.Components;
|
|||||||
using Microsoft.JSInterop;
|
using Microsoft.JSInterop;
|
||||||
using MudBlazor;
|
using MudBlazor;
|
||||||
using System.ComponentModel.DataAnnotations;
|
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;
|
using Severity = MudBlazor.Severity;
|
||||||
|
|
||||||
namespace FrontOffice.Main.Pages.Profile;
|
namespace FrontOffice.Main.Pages.Profile;
|
||||||
@@ -21,10 +22,11 @@ public partial class Index
|
|||||||
[Inject] private UserAddressContract.UserAddressContractClient UserAddressContract { get; set; } = default!;
|
[Inject] private UserAddressContract.UserAddressContractClient UserAddressContract { get; set; } = default!;
|
||||||
[Inject] private PackageContract.PackageContractClient PackageContract { get; set; } = default!;
|
[Inject] private PackageContract.PackageContractClient PackageContract { get; set; } = default!;
|
||||||
[Inject] private AuthService AuthService { get; set; } = default!;
|
[Inject] private AuthService AuthService { get; set; } = default!;
|
||||||
|
[Inject] private BlogPostService BlogPostService { get; set; } = default!;
|
||||||
|
|
||||||
private GetUserResponse _userProfile = new();
|
private GetUserForCustomerResponse _userProfile = new();
|
||||||
private UpdateUserRequest _updateUserRequest = new();
|
private BlogPostCardDto? _latestPost;
|
||||||
private readonly UpdateUserRequestValidator _personalValidator = new();
|
private UpdateCustomerProfileRequest _updateUserRequest = new();
|
||||||
|
|
||||||
private MudForm? _personalForm;
|
private MudForm? _personalForm;
|
||||||
|
|
||||||
@@ -40,12 +42,29 @@ public partial class Index
|
|||||||
private bool _isClubMemberActive;
|
private bool _isClubMemberActive;
|
||||||
private bool CanShowReferralLink => _hasPurchasedPackage && _isClubMemberActive;
|
private bool CanShowReferralLink => _hasPurchasedPackage && _isClubMemberActive;
|
||||||
|
|
||||||
// Purchase Options Bottom Sheet
|
// Purchase flow
|
||||||
private bool _showPurchaseBottomSheet;
|
|
||||||
private bool _isProcessingPayment;
|
private bool _isProcessingPayment;
|
||||||
|
private int _purchaseCycleCount; // تعداد دورهای خرید — اگر > 0 فقط IPG مجاز
|
||||||
|
|
||||||
|
// 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.MagicWallet, Icons.Material.Filled.AutoAwesome, "کیفپول جادویی", "شارژ چندبرابری", "background:rgba(168,85,247,.12); color:#a855f7;"),
|
||||||
|
new(RouteConstants.Profile.WithdrawalRequests, Icons.Material.Filled.RequestPage, "درخواست برداشت", "ثبت و پیگیری", "background:rgba(245,158,11,.12); color:#f59e0b;"),
|
||||||
|
new(RouteConstants.Club.Membership, Icons.Material.Filled.CardMembership, "باشگاه مشتریان", "عضویت و مزایا", "background:rgba(168,85,247,.12); color:#a855f7;"),
|
||||||
|
new(RouteConstants.Network.Statistics, Icons.Material.Filled.Groups, "آمار باشگاه", "جزئیات باشگاه", "background:rgba(59,130,246,.12); color:#3b82f6;"),
|
||||||
|
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
|
// Address management
|
||||||
private List<GetAllUserAddressByFilterResponseModel> _addresses = new();
|
private List<CustomerAddressModel> _addresses = new();
|
||||||
private bool _isLoadingAddresses;
|
private bool _isLoadingAddresses;
|
||||||
private CreateNewUserAddressRequest _newAddressRequest = new();
|
private CreateNewUserAddressRequest _newAddressRequest = new();
|
||||||
private UpdateUserAddressRequest _editAddressRequest = new();
|
private UpdateUserAddressRequest _editAddressRequest = new();
|
||||||
@@ -63,6 +82,7 @@ public partial class Index
|
|||||||
await LoadUserProfile();
|
await LoadUserProfile();
|
||||||
await LoadAddresses();
|
await LoadAddresses();
|
||||||
await LoadWallet();
|
await LoadWallet();
|
||||||
|
await LoadLatestPost();
|
||||||
|
|
||||||
// نمایش Modal قرارداد باشگاه اگر پکیج خریده ولی باشگاه فعال نشده
|
// نمایش Modal قرارداد باشگاه اگر پکیج خریده ولی باشگاه فعال نشده
|
||||||
await CheckAndShowClubContractModal();
|
await CheckAndShowClubContractModal();
|
||||||
@@ -128,24 +148,57 @@ public partial class Index
|
|||||||
var discount = FormatPrice(b.DiscountBalance);
|
var discount = FormatPrice(b.DiscountBalance);
|
||||||
_walletNetwork = FormatPrice(b.NetworkBalance);
|
_walletNetwork = FormatPrice(b.NetworkBalance);
|
||||||
_walletDiscount = discount;
|
_walletDiscount = discount;
|
||||||
|
|
||||||
|
// بارگذاری تعداد دورهای خرید برای کنترل روشهای پرداخت
|
||||||
|
var magicStatus = await WalletService.GetMagicWalletStatusAsync();
|
||||||
|
_purchaseCycleCount = magicStatus.PurchaseCycleCount;
|
||||||
|
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string FormatPrice(long price) => string.Format("{0:N0} تومان", price);
|
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()
|
private async Task LoadUserProfile()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_userProfile = await UserContract.GetUserAsync(request: new());
|
_userProfile = await UserContract.GetUserForCustomerAsync(new());
|
||||||
_updateUserRequest = _userProfile.Adapt<UpdateUserRequest>();
|
_updateUserRequest = new UpdateCustomerProfileRequest
|
||||||
|
{
|
||||||
|
FirstName = _userProfile.FirstName,
|
||||||
|
LastName = _userProfile.LastName,
|
||||||
|
Email = _userProfile.Email,
|
||||||
|
NationalCode = _userProfile.NationalCode,
|
||||||
|
};
|
||||||
if (_userProfile.BirthDate != null)
|
if (_userProfile.BirthDate != null)
|
||||||
|
{
|
||||||
_date = _userProfile.BirthDate.ToDateTime();
|
_date = _userProfile.BirthDate.ToDateTime();
|
||||||
|
_updateUserRequest.BirthDate = _userProfile.BirthDate;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
// Handle the case when user is not authenticated or API fails
|
// Handle the case when user is not authenticated or API fails
|
||||||
_userProfile = new GetUserResponse();
|
_userProfile = new GetUserForCustomerResponse();
|
||||||
}
|
}
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
@@ -167,7 +220,7 @@ public partial class Index
|
|||||||
if (_date != null)
|
if (_date != null)
|
||||||
_updateUserRequest.BirthDate = _date.Value.DateTimeToTimestamp();
|
_updateUserRequest.BirthDate = _date.Value.DateTimeToTimestamp();
|
||||||
|
|
||||||
await UserContract.UpdateUserAsync(request: _updateUserRequest);
|
await UserContract.UpdateCustomerProfileAsync(request: _updateUserRequest);
|
||||||
await LoadUserProfile();
|
await LoadUserProfile();
|
||||||
Snackbar.Add("اطلاعات شخصی با موفقیت ذخیره شد.", Severity.Success);
|
Snackbar.Add("اطلاعات شخصی با موفقیت ذخیره شد.", Severity.Success);
|
||||||
}
|
}
|
||||||
@@ -250,20 +303,20 @@ public partial class Index
|
|||||||
_isLoadingAddresses = true;
|
_isLoadingAddresses = true;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var response = await UserAddressContract.GetAllUserAddressByFilterAsync(request: new());
|
var response = await UserAddressContract.GetCustomerAddressesAsync(request: new());
|
||||||
if (response?.Models?.Any() == true)
|
if (response?.Models?.Any() == true)
|
||||||
{
|
{
|
||||||
_addresses = response.Models.ToList();
|
_addresses = response.Models.ToList();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_addresses = new List<GetAllUserAddressByFilterResponseModel>();
|
_addresses = new List<CustomerAddressModel>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Snackbar.Add($"خطا در بارگذاری آدرسها: {ex.Message}", Severity.Error);
|
Snackbar.Add($"خطا در بارگذاری آدرسها: {ex.Message}", Severity.Error);
|
||||||
_addresses = new List<GetAllUserAddressByFilterResponseModel>();
|
_addresses = new List<CustomerAddressModel>();
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -280,10 +333,11 @@ public partial class Index
|
|||||||
if (!result.Canceled)
|
if (!result.Canceled)
|
||||||
{
|
{
|
||||||
await LoadAddresses();
|
await LoadAddresses();
|
||||||
|
await AuthService.RefreshTokenAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task OpenEditAddressDialog(GetAllUserAddressByFilterResponseModel address)
|
private async Task OpenEditAddressDialog(CustomerAddressModel address)
|
||||||
{
|
{
|
||||||
var dialog = await DialogService.ShowAsync<EditAddressDialog>("ویرایش آدرس", new DialogParameters<EditAddressDialog>
|
var dialog = await DialogService.ShowAsync<EditAddressDialog>("ویرایش آدرس", new DialogParameters<EditAddressDialog>
|
||||||
{
|
{
|
||||||
@@ -332,6 +386,7 @@ public partial class Index
|
|||||||
});
|
});
|
||||||
Snackbar.Add("آدرس با موفقیت حذف شد.", Severity.Success);
|
Snackbar.Add("آدرس با موفقیت حذف شد.", Severity.Success);
|
||||||
await LoadAddresses();
|
await LoadAddresses();
|
||||||
|
await AuthService.RefreshTokenAsync();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -340,41 +395,62 @@ public partial class Index
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OpenPurchaseOptions()
|
private async Task OpenPurchaseOptions()
|
||||||
{
|
{
|
||||||
_showPurchaseBottomSheet = true;
|
var options = new DialogOptions
|
||||||
|
{
|
||||||
|
MaxWidth = MaxWidth.Small,
|
||||||
|
FullWidth = true,
|
||||||
|
CloseOnEscapeKey = true,
|
||||||
|
BackdropClick = true
|
||||||
|
};
|
||||||
|
|
||||||
|
var parameters = new DialogParameters<Shared.PackagePurchaseDialog>
|
||||||
|
{
|
||||||
|
{ x => x.PurchaseCycleCount, _purchaseCycleCount }
|
||||||
|
};
|
||||||
|
|
||||||
|
var dialog = await DialogService.ShowAsync<Shared.PackagePurchaseDialog>(
|
||||||
|
string.Empty, parameters, options);
|
||||||
|
var result = await dialog.Result;
|
||||||
|
|
||||||
|
if (result.Canceled) return;
|
||||||
|
|
||||||
|
if (result.Data is Shared.PackagePurchaseDialog.PackagePurchaseResult purchase)
|
||||||
|
{
|
||||||
|
switch (purchase.Method)
|
||||||
|
{
|
||||||
|
case Shared.PackagePurchaseDialog.PaymentMethodType.DirectPayment:
|
||||||
|
await ProcessDirectPayment(purchase.Package.Id);
|
||||||
|
break;
|
||||||
|
case Shared.PackagePurchaseDialog.PaymentMethodType.DayaLoan:
|
||||||
|
await ProcessDayaLoanPayment();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ClosePurchaseOptions()
|
private async Task ProcessDirectPayment(long packageId)
|
||||||
{
|
|
||||||
_showPurchaseBottomSheet = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task DirectPayment()
|
|
||||||
{
|
{
|
||||||
if (_isProcessingPayment) return;
|
if (_isProcessingPayment) return;
|
||||||
|
|
||||||
_isProcessingPayment = true;
|
_isProcessingPayment = true;
|
||||||
_showPurchaseBottomSheet = false;
|
StateHasChanged();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Create callback URL for payment verification
|
var response = await PackageContract.CustomerPurchasePackageAsync(new CustomerPurchasePackageRequest
|
||||||
var callbackUrl = $"{Navigation.BaseUri}profile/payment-callback";
|
|
||||||
|
|
||||||
// Call BFF to initiate payment (UserId is taken from JWT token in BFF)
|
|
||||||
var response = await PackageContract.InitiateBasePackagePaymentAsync(new InitiateBasePackagePaymentRequest
|
|
||||||
{
|
{
|
||||||
CallbackUrl = callbackUrl
|
PackageId = packageId,
|
||||||
|
PurchaseMethod = PurchaseMethodEnum.PurchaseMethodGateway
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.Success)
|
if (!response.Success)
|
||||||
{
|
{
|
||||||
Snackbar.Add(response.Message ?? "خطا در آغاز فرآیند پرداخت", Severity.Error);
|
Snackbar.Add(response.Message ?? "خطا در آغاز فرآیند پرداخت", Severity.Error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Redirect to payment gateway
|
|
||||||
if (!string.IsNullOrEmpty(response.PaymentGatewayUrl))
|
if (!string.IsNullOrEmpty(response.PaymentGatewayUrl))
|
||||||
{
|
{
|
||||||
Navigation.NavigateTo(response.PaymentGatewayUrl, forceLoad: true);
|
Navigation.NavigateTo(response.PaymentGatewayUrl, forceLoad: true);
|
||||||
@@ -395,41 +471,29 @@ public partial class Index
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task DayaLoanPayment()
|
private async Task ProcessDayaLoanPayment()
|
||||||
{
|
{
|
||||||
// Validate user info before redirecting
|
|
||||||
if (string.IsNullOrWhiteSpace(_updateUserRequest.FirstName))
|
if (string.IsNullOrWhiteSpace(_updateUserRequest.FirstName))
|
||||||
{
|
{
|
||||||
_showPurchaseBottomSheet = false;
|
Snackbar.Add("لطفا اطلاعات شخصی خود را تکمیل کنید. (نام وارد نشده)", Severity.Error);
|
||||||
Snackbar.Add($"لطفا اطلاعات شخصی خود را تکمیل کنید. (نام وارد نشده)", Severity.Error);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (string.IsNullOrWhiteSpace(_updateUserRequest.LastName))
|
if (string.IsNullOrWhiteSpace(_updateUserRequest.LastName))
|
||||||
{
|
{
|
||||||
_showPurchaseBottomSheet = false;
|
Snackbar.Add("لطفا اطلاعات شخصی خود را تکمیل کنید. (نام خانوادگی وارد نشده)", Severity.Error);
|
||||||
Snackbar.Add($"لطفا اطلاعات شخصی خود را تکمیل کنید. (نام خانوادگی وارد نشده)", Severity.Error);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (string.IsNullOrWhiteSpace(_updateUserRequest.NationalCode))
|
if (string.IsNullOrWhiteSpace(_updateUserRequest.NationalCode))
|
||||||
{
|
{
|
||||||
_showPurchaseBottomSheet = false;
|
Snackbar.Add("لطفا اطلاعات شخصی خود را تکمیل کنید. (کدملی وارد نشده)", Severity.Error);
|
||||||
Snackbar.Add($"لطفا اطلاعات شخصی خود را تکمیل کنید. (کدملی وارد نشده)", Severity.Error);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (_updateUserRequest.BirthDate == null)
|
if (_updateUserRequest.BirthDate == null)
|
||||||
{
|
{
|
||||||
_showPurchaseBottomSheet = false;
|
Snackbar.Add("لطفا اطلاعات شخصی خود را تکمیل کنید. (تاریخ تولد وارد نشده)", Severity.Error);
|
||||||
Snackbar.Add($"لطفا اطلاعات شخصی خود را تکمیل کنید. (تاریخ تولد وارد نشده)", Severity.Error);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!_addresses.Any())
|
|
||||||
{
|
|
||||||
_showPurchaseBottomSheet = false;
|
|
||||||
Snackbar.Add($"آدرس محل سکونت شما الزامی است!", Severity.Error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_showPurchaseBottomSheet = false;
|
|
||||||
var url = "https://dayadiamond.ir/profile/creditpurchase/?merchantcode=56146364";
|
var url = "https://dayadiamond.ir/profile/creditpurchase/?merchantcode=56146364";
|
||||||
await JSRuntime.InvokeVoidAsync("open", url, "_blank");
|
await JSRuntime.InvokeVoidAsync("open", url, "_blank");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,229 @@
|
|||||||
|
@attribute [Route(RouteConstants.Profile.MagicWallet)]
|
||||||
|
@attribute [Authorize]
|
||||||
|
|
||||||
|
<PageTitle>کیفپول جادویی</PageTitle>
|
||||||
|
|
||||||
|
<MudContainer MaxWidth="MaxWidth.Medium" Class="py-6">
|
||||||
|
<MudStack Spacing="3">
|
||||||
|
<PageHeader Title="🪄 کیفپول جادویی" BackHref="@RouteConstants.Profile.Wallet" />
|
||||||
|
|
||||||
|
@if (_isLoading)
|
||||||
|
{
|
||||||
|
<MudPaper Elevation="2" Class="pa-6 rounded-lg text-center">
|
||||||
|
<MudProgressCircular Color="Color.Primary" Indeterminate="true" />
|
||||||
|
<MudText Class="mt-3">در حال بارگذاری...</MudText>
|
||||||
|
</MudPaper>
|
||||||
|
}
|
||||||
|
else if (_status.WalletMode != 1)
|
||||||
|
{
|
||||||
|
@* ═══ حالت عادی — Magic فعال نیست ═══ *@
|
||||||
|
<MudPaper Elevation="2" Class="pa-6 rounded-lg text-center">
|
||||||
|
<MudIcon Icon="@Icons.Material.Filled.Lock" Color="Color.Default" Size="Size.Large" Style="font-size: 64px;" />
|
||||||
|
<MudText Typo="Typo.h5" Class="mt-4">کیفپول جادویی فعال نیست</MudText>
|
||||||
|
<MudText Typo="Typo.body1" Class="mt-2 mud-text-secondary">
|
||||||
|
برای فعال شدن کیفپول جادویی، ابتدا پکیج پایه را خریداری کنید و سپس
|
||||||
|
تمام موجودی اصلی خود را از فروشگاه خرج کنید. وقتی موجودی اصلی به صفر برسد،
|
||||||
|
کیفپول جادویی فعال میشود.
|
||||||
|
</MudText>
|
||||||
|
<MudButton Variant="Variant.Filled" Color="Color.Primary" Class="mt-4 rounded-pill"
|
||||||
|
Href="@RouteConstants.Profile.Wallet"
|
||||||
|
StartIcon="@Icons.Material.Filled.AccountBalanceWallet">
|
||||||
|
بازگشت به کیف پول
|
||||||
|
</MudButton>
|
||||||
|
</MudPaper>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
@* ═══ حالت جادویی فعال ═══ *@
|
||||||
|
|
||||||
|
@* ── G4: هشدار تنظیمات پکیجبیس (Q28) ── *@
|
||||||
|
<MudAlert Severity="Severity.Warning" Variant="Variant.Text" Dense="true" Icon="@Icons.Material.Filled.Warning">
|
||||||
|
سقف واریز و ضریب اعتبار کیفپول جادویی بر اساس پکیج شما تنظیم شده است. پکیجهای بالاتر سقف و ضریب بیشتری دارند.
|
||||||
|
</MudAlert>
|
||||||
|
|
||||||
|
@* ─── وضعیت و پیشرفت ─── *@
|
||||||
|
<MudPaper Elevation="2" Class="pa-5 rounded-xl" Style="background: linear-gradient(135deg, #7c3aed 0%, #a855f7 100%); color: white;">
|
||||||
|
<MudStack Spacing="2">
|
||||||
|
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
|
||||||
|
<MudIcon Icon="@Icons.Material.Filled.AutoAwesome" Size="Size.Large" />
|
||||||
|
<MudText Typo="Typo.h5">حالت جادویی فعال</MudText>
|
||||||
|
</MudStack>
|
||||||
|
|
||||||
|
<MudGrid Spacing="2">
|
||||||
|
<MudItem xs="6" sm="3">
|
||||||
|
<MudText Typo="Typo.caption" Style="opacity:0.8">موجودی فعلی</MudText>
|
||||||
|
<MudText Typo="Typo.h6">@FormatPrice(_status.Balance)</MudText>
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="6" sm="3">
|
||||||
|
<MudText Typo="Typo.caption" Style="opacity:0.8">مجموع واریزی</MudText>
|
||||||
|
<MudText Typo="Typo.h6">@FormatPrice(_status.MagicTotalDeposited)</MudText>
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="6" sm="3">
|
||||||
|
<MudText Typo="Typo.caption" Style="opacity:0.8">مجموع اعتبار</MudText>
|
||||||
|
<MudText Typo="Typo.h6">@FormatPrice(_status.MagicTotalCredited)</MudText>
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="6" sm="3">
|
||||||
|
<MudText Typo="Typo.caption" Style="opacity:0.8">سقف باقیمانده</MudText>
|
||||||
|
<MudText Typo="Typo.h6">@FormatPrice(_status.MagicRemainingDeposit)</MudText>
|
||||||
|
</MudItem>
|
||||||
|
</MudGrid>
|
||||||
|
|
||||||
|
@* نوار پیشرفت *@
|
||||||
|
<MudStack Spacing="0" Class="mt-1">
|
||||||
|
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||||||
|
<MudText Typo="Typo.caption" Style="opacity:0.8">پیشرفت سقف واریز</MudText>
|
||||||
|
<MudText Typo="Typo.caption" Style="opacity:0.8">@(_progressPercent.ToString("0"))%</MudText>
|
||||||
|
</MudStack>
|
||||||
|
<MudProgressLinear Value="@_progressPercent" Color="Color.Warning" Size="Size.Medium"
|
||||||
|
Rounded="true" Class="mt-1" Style="background: rgba(255,255,255,0.3);" />
|
||||||
|
</MudStack>
|
||||||
|
|
||||||
|
@if (_status.MagicActivatedAt.HasValue)
|
||||||
|
{
|
||||||
|
<MudText Typo="Typo.caption" Style="opacity:0.7">
|
||||||
|
فعال از: @_status.MagicActivatedAt.Value.MiladiToJalaliWithTime()
|
||||||
|
</MudText>
|
||||||
|
}
|
||||||
|
</MudStack>
|
||||||
|
</MudPaper>
|
||||||
|
|
||||||
|
@* ─── فرم شارژ ─── *@
|
||||||
|
@if (_status.MagicRemainingDeposit > 0)
|
||||||
|
{
|
||||||
|
<MudPaper Elevation="2" Class="pa-5 rounded-lg">
|
||||||
|
<MudText Typo="Typo.h6" Class="mb-3">
|
||||||
|
<MudIcon Icon="@Icons.Material.Filled.CreditCard" Class="ml-1" />
|
||||||
|
شارژ کیفپول جادویی
|
||||||
|
</MudText>
|
||||||
|
|
||||||
|
<MudAlert Severity="MudBlazor.Severity.Info" Dense="true" Class="mb-3" Icon="@Icons.Material.Filled.Info">
|
||||||
|
مبلغ واریزی شما <strong>×@_status.MagicMultiplier.ToString("0.#")</strong> به موجودی اضافه میشود.
|
||||||
|
مثلاً ۱۰ میلیون واریز = @FormatToman((long)(10_000_000 * (decimal)_status.MagicMultiplier)) اعتبار.
|
||||||
|
</MudAlert>
|
||||||
|
|
||||||
|
<MudStack Spacing="2">
|
||||||
|
<MudNumericField @bind-Value="_chargeAmount"
|
||||||
|
Label="مبلغ واریز (تومان)"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Min="10_000"
|
||||||
|
Max="@_status.MagicRemainingDeposit"
|
||||||
|
Format="N0"
|
||||||
|
Adornment="Adornment.Start"
|
||||||
|
AdornmentIcon="@Icons.Material.Filled.Payments"
|
||||||
|
HelperText="@($"حداکثر: {FormatPrice(_status.MagicRemainingDeposit)} (تومان)")" />
|
||||||
|
|
||||||
|
@if (_chargeAmount > 0)
|
||||||
|
{
|
||||||
|
<MudPaper Outlined="true" Class="pa-3 rounded-lg" Style="background: #f0fdf4;">
|
||||||
|
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||||||
|
<MudText Typo="Typo.body2">مبلغ واریز:</MudText>
|
||||||
|
<MudText Typo="Typo.body2">@FormatToman(_chargeAmount)</MudText>
|
||||||
|
</MudStack>
|
||||||
|
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||||||
|
<MudText Typo="Typo.body2" Color="Color.Success"><strong>اعتبار دریافتی (×@_status.MagicMultiplier.ToString("0.#")):</strong></MudText>
|
||||||
|
<MudText Typo="Typo.body2" Color="Color.Success"><strong>@FormatToman((long)(_chargeAmount * (decimal)_status.MagicMultiplier))</strong></MudText>
|
||||||
|
</MudStack>
|
||||||
|
</MudPaper>
|
||||||
|
}
|
||||||
|
|
||||||
|
@* دکمههای مبلغ سریع *@
|
||||||
|
<MudStack Row="true" Spacing="1" Class="flex-wrap">
|
||||||
|
@foreach (var preset in _presetAmounts)
|
||||||
|
{
|
||||||
|
if (preset <= _status.MagicRemainingDeposit)
|
||||||
|
{
|
||||||
|
<MudButton Variant="Variant.Outlined" Size="Size.Small"
|
||||||
|
Color="@(_chargeAmount == preset ? Color.Primary : Color.Default)"
|
||||||
|
OnClick="() => _chargeAmount = preset"
|
||||||
|
Class="rounded-pill">
|
||||||
|
@FormatToman(preset)
|
||||||
|
</MudButton>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</MudStack>
|
||||||
|
|
||||||
|
<MudButton Variant="Variant.Filled"
|
||||||
|
Color="Color.Primary"
|
||||||
|
Size="Size.Large"
|
||||||
|
FullWidth="true"
|
||||||
|
Disabled="@(_isProcessing || _chargeAmount < 10_000)"
|
||||||
|
OnClick="StartMagicCharge"
|
||||||
|
StartIcon="@Icons.Material.Filled.Payment"
|
||||||
|
Class="rounded-lg mt-2">
|
||||||
|
@if (_isProcessing)
|
||||||
|
{
|
||||||
|
<MudProgressCircular Size="Size.Small" Indeterminate="true" Class="me-2" />
|
||||||
|
<span>در حال انتقال به درگاه...</span>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span>پرداخت با کارت بانکی</span>
|
||||||
|
}
|
||||||
|
</MudButton>
|
||||||
|
</MudStack>
|
||||||
|
</MudPaper>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudAlert Severity="MudBlazor.Severity.Warning" Class="rounded-lg">
|
||||||
|
<MudStack Spacing="2">
|
||||||
|
<MudText>
|
||||||
|
سقف شارژ جادویی در این دور پر شده است.
|
||||||
|
با خرج کردن موجودی و خرید مجدد پکیج، سقف ریست میشود.
|
||||||
|
</MudText>
|
||||||
|
<MudText Typo="Typo.body2">
|
||||||
|
اگر برای تکمیل خرید به موجودی بیشتری نیاز دارید، میتوانید بهصورت استثنایی کیف اصلی را بدون ضریب (۱:۱) شارژ کنید.
|
||||||
|
</MudText>
|
||||||
|
<MudButton Variant="Variant.Filled"
|
||||||
|
Color="Color.Primary"
|
||||||
|
Size="Size.Small"
|
||||||
|
StartIcon="@Icons.Material.Filled.AccountBalanceWallet"
|
||||||
|
Href="@RouteConstants.Profile.ChargeCreditWallet"
|
||||||
|
Class="rounded-lg align-self-start">
|
||||||
|
شارژ عادی بدون ضریب
|
||||||
|
</MudButton>
|
||||||
|
</MudStack>
|
||||||
|
</MudAlert>
|
||||||
|
}
|
||||||
|
|
||||||
|
@* ─── قوانین ─── *@
|
||||||
|
<MudExpansionPanels Elevation="1" Class="rounded-lg">
|
||||||
|
<MudExpansionPanel Text="قوانین کیفپول جادویی" MaxHeight="500" IsInitiallyExpanded="false">
|
||||||
|
<MudList T="string" Dense="true">
|
||||||
|
<MudListItem T="string" Icon="@Icons.Material.Filled.CheckCircle" IconColor="Color.Success">
|
||||||
|
هر مبلغ واریزی ×@_status.MagicMultiplier.ToString("0.#") به موجودی اضافه میشود
|
||||||
|
</MudListItem>
|
||||||
|
<MudListItem T="string" Icon="@Icons.Material.Filled.CheckCircle" IconColor="Color.Success">
|
||||||
|
سقف واریز در هر دور: @FormatPrice(_status.MagicMaxDeposit)
|
||||||
|
</MudListItem>
|
||||||
|
<MudListItem T="string" Icon="@Icons.Material.Filled.CheckCircle" IconColor="Color.Success">
|
||||||
|
حداکثر اعتبار: @FormatPrice(_status.MagicMaxCredit)
|
||||||
|
</MudListItem>
|
||||||
|
<MudListItem T="string" Icon="@Icons.Material.Filled.Warning" IconColor="Color.Warning">
|
||||||
|
در حالت جادویی، کمیسیون و پاداش های دریافتی غیرفعال است
|
||||||
|
</MudListItem>
|
||||||
|
<MudListItem T="string" Icon="@Icons.Material.Filled.Info" IconColor="Color.Info">
|
||||||
|
بعد از اتمام سقف و خرج موجودی، با خرید مجدد پکیج دور جدید شروع میشود
|
||||||
|
</MudListItem>
|
||||||
|
</MudList>
|
||||||
|
</MudExpansionPanel>
|
||||||
|
</MudExpansionPanels>
|
||||||
|
}
|
||||||
|
|
||||||
|
@* ─── نتیجه پرداخت (اگه از callback برگشته) ─── *@
|
||||||
|
@if (_paymentResult != null)
|
||||||
|
{
|
||||||
|
<MudAlert Severity="@(_paymentResult == "success" ? MudBlazor.Severity.Success : MudBlazor.Severity.Error)"
|
||||||
|
Class="rounded-lg" Variant="Variant.Filled">
|
||||||
|
@if (_paymentResult == "success")
|
||||||
|
{
|
||||||
|
<span>شارژ جادویی با موفقیت انجام شد! ✅</span>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span>پرداخت ناموفق بود. لطفاً دوباره تلاش کنید.</span>
|
||||||
|
}
|
||||||
|
</MudAlert>
|
||||||
|
}
|
||||||
|
</MudStack>
|
||||||
|
</MudContainer>
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
using DateTimeConverterCL;
|
||||||
|
using FrontOffice.Main.Utilities;
|
||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using MudBlazor;
|
||||||
|
|
||||||
|
namespace FrontOffice.Main.Pages.Profile;
|
||||||
|
|
||||||
|
public partial class MagicWallet : ComponentBase
|
||||||
|
{
|
||||||
|
private MagicWalletStatus _status = new(0, 0, 0, 0, 0, 0, null, 0);
|
||||||
|
private bool _isLoading = true;
|
||||||
|
private bool _isProcessing;
|
||||||
|
private long _chargeAmount;
|
||||||
|
private double _progressPercent;
|
||||||
|
private string? _paymentResult;
|
||||||
|
|
||||||
|
private readonly long[] _presetAmounts = { 1_000_000, 5_000_000, 10_000_000, 20_000_000, 50_000_000, 100_000_000 };
|
||||||
|
|
||||||
|
[SupplyParameterFromQuery(Name = "payment")]
|
||||||
|
public string? PaymentQueryParam { get; set; }
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
_paymentResult = PaymentQueryParam;
|
||||||
|
await LoadStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoadStatus()
|
||||||
|
{
|
||||||
|
_isLoading = true;
|
||||||
|
_status = await WalletService.GetMagicWalletStatusAsync();
|
||||||
|
|
||||||
|
_progressPercent = _status.MagicMaxDeposit > 0
|
||||||
|
? (double)_status.MagicTotalDeposited / _status.MagicMaxDeposit * 100
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
_isLoading = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task StartMagicCharge()
|
||||||
|
{
|
||||||
|
if (_chargeAmount <= 0 || _isProcessing) return;
|
||||||
|
|
||||||
|
_isProcessing = true;
|
||||||
|
StateHasChanged();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// مبلغ به تومان — CMS خودش موقع ارسال به درگاه ×۱۰ میکنه
|
||||||
|
var (success, gatewayUrl, error) = await WalletService.InitiateMagicChargeAsync(_chargeAmount);
|
||||||
|
|
||||||
|
if (success && !string.IsNullOrEmpty(gatewayUrl))
|
||||||
|
{
|
||||||
|
Navigation.NavigateTo(gatewayUrl, forceLoad: true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Snackbar.Add(error ?? "خطا در ایجاد درخواست پرداخت", Severity.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Snackbar.Add($"خطا: {ex.Message}", Severity.Error);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_isProcessing = false;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string FormatPrice(long price)
|
||||||
|
=> string.Format("{0:N0} تومان", price);
|
||||||
|
|
||||||
|
private static string FormatToman(long toman)
|
||||||
|
=> string.Format("{0:N0} تومان", toman);
|
||||||
|
}
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
@page "/profile/payment-callback"
|
@page "/profile/payment-callback"
|
||||||
@attribute [Authorize]
|
@attribute [Authorize]
|
||||||
@using Blazored.LocalStorage
|
@using Blazored.LocalStorage
|
||||||
@using FrontOffice.BFF.Package.Protobuf.Protos.Package
|
@using CMSMicroservice.Protobuf.Protos.Package
|
||||||
@using FrontOffice.BFF.User.Protobuf.Protos.User
|
@using CMSMicroservice.Protobuf.Protos.User
|
||||||
|
@using FrontOffice.Main.Utilities
|
||||||
|
|
||||||
|
|
||||||
<PageTitle>نتیجه پرداخت | کارا بازار سلامت</PageTitle>
|
<PageTitle>نتیجه پرداخت | کارا بازار سلامت</PageTitle>
|
||||||
|
|
||||||
@@ -19,25 +21,22 @@
|
|||||||
<MudText Typo="Typo.h5" Color="Color.Success" Class="mt-4">پرداخت موفق</MudText>
|
<MudText Typo="Typo.h5" Color="Color.Success" Class="mt-4">پرداخت موفق</MudText>
|
||||||
<MudText Typo="Typo.body1" Class="mt-2">@_message</MudText>
|
<MudText Typo="Typo.body1" Class="mt-2">@_message</MudText>
|
||||||
|
|
||||||
@if (!string.IsNullOrEmpty(_refId))
|
@if (_transactionId > 0)
|
||||||
{
|
{
|
||||||
<MudText Typo="Typo.body2" Class="mt-3">
|
<MudText Typo="Typo.body2" Class="mt-3">
|
||||||
<strong>کد پیگیری:</strong> @_refId
|
<strong>کد ارجاعی:</strong> @_transactionId
|
||||||
</MudText>
|
</MudText>
|
||||||
}
|
}
|
||||||
|
|
||||||
<MudDivider Class="my-4" />
|
<MudDivider Class="my-4" />
|
||||||
|
|
||||||
<MudText Typo="Typo.body2" Color="Color.Secondary">
|
<MudText Typo="Typo.body2" Color="Color.Secondary">
|
||||||
موجودی کیف پول: @FormatPrice(_walletBalance)
|
موجودی کیف پول اصلی: @FormatPrice(_walletBalance)
|
||||||
</MudText>
|
|
||||||
<MudText Typo="Typo.body2" Color="Color.Secondary">
|
|
||||||
موجودی اعتبار تخفیف: @FormatPrice(_discountBalance)
|
|
||||||
</MudText>
|
</MudText>
|
||||||
|
|
||||||
<MudButton Color="Color.Primary" Variant="Variant.Filled" Class="mt-6"
|
<MudButton Color="Color.Primary" Variant="Variant.Filled" Class="mt-6"
|
||||||
Href="/profile">
|
Href="@_successReturnUrl">
|
||||||
بازگشت به پروفایل
|
@_successReturnText
|
||||||
</MudButton>
|
</MudButton>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -47,8 +46,8 @@
|
|||||||
<MudText Typo="Typo.body1" Class="mt-2">@_message</MudText>
|
<MudText Typo="Typo.body1" Class="mt-2">@_message</MudText>
|
||||||
|
|
||||||
<MudButton Color="Color.Primary" Variant="Variant.Filled" Class="mt-6"
|
<MudButton Color="Color.Primary" Variant="Variant.Filled" Class="mt-6"
|
||||||
Href="/profile">
|
Href="@_failReturnUrl">
|
||||||
بازگشت به پروفایل
|
@_failReturnText
|
||||||
</MudButton>
|
</MudButton>
|
||||||
|
|
||||||
<MudButton Color="Color.Secondary" Variant="Variant.Outlined" Class="mt-3 mr-2"
|
<MudButton Color="Color.Secondary" Variant="Variant.Outlined" Class="mt-3 mr-2"
|
||||||
@@ -62,12 +61,19 @@
|
|||||||
@code {
|
@code {
|
||||||
[Inject] private PackageContract.PackageContractClient PackageContractClient { get; set; } = default!;
|
[Inject] private PackageContract.PackageContractClient PackageContractClient { get; set; } = default!;
|
||||||
[Inject] private UserContract.UserContractClient UserContractClient { get; set; } = default!;
|
[Inject] private UserContract.UserContractClient UserContractClient { get; set; } = default!;
|
||||||
|
[Inject] private DiscountOrderService DiscountOrderService { get; set; } = default!;
|
||||||
[Inject] private ILocalStorageService LocalStorage { get; set; } = default!;
|
[Inject] private ILocalStorageService LocalStorage { get; set; } = default!;
|
||||||
[Inject] private AuthService AuthService { get; set; } = default!;
|
[Inject] private AuthService AuthService { get; set; } = default!;
|
||||||
[Inject] private NavigationManager NavManager { get; set; } = default!;
|
[Inject] private NavigationManager NavManager { get; set; } = default!;
|
||||||
|
|
||||||
private const string TokenStorageKey = "auth:token";
|
private const string TokenStorageKey = "auth:token";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// نوع پرداخت: package (پیشفرض) | magic-wallet | discount-wallet | discount-order
|
||||||
|
/// </summary>
|
||||||
|
[SupplyParameterFromQuery(Name = "type")]
|
||||||
|
private string? PaymentType { get; set; }
|
||||||
|
|
||||||
[SupplyParameterFromQuery(Name = "orderId")]
|
[SupplyParameterFromQuery(Name = "orderId")]
|
||||||
private long OrderId { get; set; }
|
private long OrderId { get; set; }
|
||||||
|
|
||||||
@@ -83,15 +89,42 @@
|
|||||||
private bool _isLoading = true;
|
private bool _isLoading = true;
|
||||||
private bool _isSuccess;
|
private bool _isSuccess;
|
||||||
private string _message = string.Empty;
|
private string _message = string.Empty;
|
||||||
private string? _refId;
|
private long _transactionId;
|
||||||
private long _walletBalance;
|
private long _walletBalance;
|
||||||
private long _discountBalance;
|
private bool _verifyCompleted;
|
||||||
|
private readonly SemaphoreSlim _verifyGate = new(1, 1);
|
||||||
|
|
||||||
|
// دکمههای بازگشت بسته به نوع پرداخت
|
||||||
|
private string _successReturnUrl = "/profile";
|
||||||
|
private string _successReturnText = "بازگشت به پروفایل";
|
||||||
|
private string _failReturnUrl = "/profile";
|
||||||
|
private string _failReturnText = "بازگشت به پروفایل";
|
||||||
|
|
||||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
{
|
{
|
||||||
if (firstRender)
|
if (firstRender && !_verifyCompleted)
|
||||||
{
|
{
|
||||||
|
await VerifyPaymentOnceAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Blazor Server may invoke render/verify twice in quick succession — gate with semaphore.
|
||||||
|
/// </summary>
|
||||||
|
private async Task VerifyPaymentOnceAsync()
|
||||||
|
{
|
||||||
|
if (_verifyCompleted) return;
|
||||||
|
|
||||||
|
await _verifyGate.WaitAsync();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_verifyCompleted) return;
|
||||||
await VerifyPayment();
|
await VerifyPayment();
|
||||||
|
_verifyCompleted = true;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_verifyGate.Release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,34 +132,26 @@
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (OrderId <= 0 || TransactionId <= 0 || string.IsNullOrEmpty(Authority))
|
// تعیین نوع پرداخت — اگر type نباشد، پیشفرض خرید پکیج
|
||||||
{
|
var type = PaymentType?.ToLowerInvariant() ?? "package";
|
||||||
_isSuccess = false;
|
|
||||||
_message = "پارامترهای پرداخت نامعتبر است";
|
|
||||||
_isLoading = false;
|
|
||||||
StateHasChanged();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var response = await PackageContractClient.VerifyBasePackagePaymentAsync(new VerifyBasePackagePaymentRequest
|
|
||||||
{
|
|
||||||
OrderId = OrderId,
|
|
||||||
TransactionId = TransactionId,
|
|
||||||
Authority = Authority,
|
|
||||||
Status = Status ?? "NOK"
|
|
||||||
});
|
|
||||||
|
|
||||||
_isSuccess = response.Success;
|
|
||||||
_message = response.Message;
|
|
||||||
_refId = response.RefId;
|
|
||||||
_walletBalance = response.WalletBalance;
|
|
||||||
_discountBalance = response.DiscountBalance;
|
|
||||||
|
|
||||||
// اگر پرداخت موفق بود، توکن را refresh میکنیم
|
switch (type)
|
||||||
// چون PackagePurchaseMethod تغییر کرده و باید در claims جدید باشد
|
|
||||||
if (_isSuccess)
|
|
||||||
{
|
{
|
||||||
await RefreshTokenAsync();
|
case "magic-wallet":
|
||||||
|
await VerifyMagicWalletCharge();
|
||||||
|
break;
|
||||||
|
case "discount-wallet":
|
||||||
|
await VerifyDiscountWalletCharge();
|
||||||
|
break;
|
||||||
|
case "credit-wallet":
|
||||||
|
await VerifyCreditWalletCharge();
|
||||||
|
break;
|
||||||
|
case "discount-order":
|
||||||
|
await VerifyDiscountOrderPayment();
|
||||||
|
break;
|
||||||
|
default: // "package" or missing
|
||||||
|
await VerifyPackagePurchase();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -140,10 +165,186 @@
|
|||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// تأیید خرید پکیج — رفتار فعلی
|
||||||
|
/// </summary>
|
||||||
|
private async Task VerifyPackagePurchase()
|
||||||
|
{
|
||||||
|
_successReturnUrl = "/profile";
|
||||||
|
_successReturnText = "بازگشت به پروفایل";
|
||||||
|
_failReturnUrl = "/profile";
|
||||||
|
_failReturnText = "بازگشت به پروفایل";
|
||||||
|
|
||||||
|
if (OrderId <= 0 || string.IsNullOrEmpty(Authority))
|
||||||
|
{
|
||||||
|
_isSuccess = false;
|
||||||
|
_message = "پارامترهای پرداخت نامعتبر است";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var response = await PackageContractClient.CustomerVerifyPackagePurchaseAsync(new CustomerVerifyPackagePurchaseRequest
|
||||||
|
{
|
||||||
|
OrderId = OrderId,
|
||||||
|
Authority = Authority ?? string.Empty,
|
||||||
|
Status = Status ?? string.Empty
|
||||||
|
});
|
||||||
|
|
||||||
|
_isSuccess = response.Success;
|
||||||
|
_message = response.Message;
|
||||||
|
_transactionId = response.TransactionId;
|
||||||
|
|
||||||
|
if (_isSuccess)
|
||||||
|
{
|
||||||
|
await UpdateWalletBalanceAndRefreshToken();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// تأیید شارژ کیفپول جادویی
|
||||||
|
/// </summary>
|
||||||
|
private async Task VerifyMagicWalletCharge()
|
||||||
|
{
|
||||||
|
_successReturnUrl = "/profile/magic-wallet";
|
||||||
|
_successReturnText = "بازگشت به کیف پول جادویی";
|
||||||
|
_failReturnUrl = "/profile/magic-wallet";
|
||||||
|
_failReturnText = "بازگشت به کیف پول جادویی";
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(Authority))
|
||||||
|
{
|
||||||
|
_isSuccess = false;
|
||||||
|
_message = "کد Authority نامعتبر است";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var (success, message) = await WalletService.VerifyMagicChargeAsync(
|
||||||
|
Authority, Status ?? "NOK");
|
||||||
|
|
||||||
|
_isSuccess = success;
|
||||||
|
_message = message;
|
||||||
|
|
||||||
|
if (_isSuccess)
|
||||||
|
{
|
||||||
|
await UpdateWalletBalance();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// تأیید شارژ کیف پول تخفیفی
|
||||||
|
/// </summary>
|
||||||
|
private async Task VerifyDiscountWalletCharge()
|
||||||
|
{
|
||||||
|
_successReturnUrl = "/profile/charge-discount-wallet";
|
||||||
|
_successReturnText = "بازگشت به کیف پول تخفیفی";
|
||||||
|
_failReturnUrl = "/profile/charge-discount-wallet";
|
||||||
|
_failReturnText = "بازگشت به کیف پول تخفیفی";
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(Authority))
|
||||||
|
{
|
||||||
|
_isSuccess = false;
|
||||||
|
_message = "کد Authority نامعتبر است";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var (success, message) = await WalletService.VerifyDiscountChargeAsync(
|
||||||
|
Authority, Status ?? "NOK");
|
||||||
|
|
||||||
|
_isSuccess = success;
|
||||||
|
_message = message;
|
||||||
|
|
||||||
|
if (_isSuccess)
|
||||||
|
{
|
||||||
|
await UpdateWalletBalance();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// تأیید شارژ کیف پول اصلی
|
||||||
|
/// </summary>
|
||||||
|
private async Task VerifyCreditWalletCharge()
|
||||||
|
{
|
||||||
|
_successReturnUrl = "/profile/charge-credit-wallet?payment=success";
|
||||||
|
_successReturnText = "بازگشت به شارژ کیف پول اصلی";
|
||||||
|
_failReturnUrl = "/profile/charge-credit-wallet?payment=failed";
|
||||||
|
_failReturnText = "بازگشت به شارژ کیف پول اصلی";
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(Authority))
|
||||||
|
{
|
||||||
|
_isSuccess = false;
|
||||||
|
_message = "کد Authority نامعتبر است";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var (success, message) = await WalletService.VerifyCreditChargeAsync(
|
||||||
|
Authority, Status ?? "NOK");
|
||||||
|
|
||||||
|
_isSuccess = success;
|
||||||
|
_message = message;
|
||||||
|
|
||||||
|
if (_isSuccess)
|
||||||
|
{
|
||||||
|
await UpdateWalletBalance();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// تأیید پرداخت سفارش فروشگاه تخفیفی
|
||||||
|
/// </summary>
|
||||||
|
private async Task VerifyDiscountOrderPayment()
|
||||||
|
{
|
||||||
|
_successReturnUrl = $"/discount-store/order/{OrderId}";
|
||||||
|
_successReturnText = "مشاهده سفارش";
|
||||||
|
_failReturnUrl = $"/discount-store/order/{OrderId}";
|
||||||
|
_failReturnText = "مشاهده سفارش";
|
||||||
|
|
||||||
|
if (OrderId <= 0 || string.IsNullOrEmpty(Authority))
|
||||||
|
{
|
||||||
|
_isSuccess = false;
|
||||||
|
_message = "پارامترهای پرداخت نامعتبر است";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var (success, message, _) = await DiscountOrderService.VerifyDiscountOrderPaymentAsync(
|
||||||
|
OrderId, Authority, Status ?? "NOK");
|
||||||
|
|
||||||
|
_isSuccess = success;
|
||||||
|
_message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// بروزرسانی موجودی کیف پول
|
||||||
|
/// </summary>
|
||||||
|
private async Task UpdateWalletBalance()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var balances = await WalletService.GetBalancesAsync();
|
||||||
|
_walletBalance = balances.CreditBalance;
|
||||||
|
}
|
||||||
|
catch { /* اگر خطا شد، صفر نمایش بده */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// بروزرسانی موجودی + refresh توکن (فقط برای خرید پکیج)
|
||||||
|
/// </summary>
|
||||||
|
private async Task UpdateWalletBalanceAndRefreshToken()
|
||||||
|
{
|
||||||
|
await UpdateWalletBalance();
|
||||||
|
await RefreshTokenAsync();
|
||||||
|
}
|
||||||
|
|
||||||
private void RetryPayment()
|
private void RetryPayment()
|
||||||
{
|
{
|
||||||
NavManager.NavigateTo("/profile");
|
var type = PaymentType?.ToLowerInvariant() ?? "package";
|
||||||
|
var url = type switch
|
||||||
|
{
|
||||||
|
"magic-wallet" => "/profile/magic-wallet",
|
||||||
|
"discount-wallet" => "/profile/charge-discount-wallet",
|
||||||
|
"credit-wallet" => "/profile/charge-credit-wallet",
|
||||||
|
"discount-order" => "/discount-store",
|
||||||
|
_ => "/profile"
|
||||||
|
};
|
||||||
|
NavManager.NavigateTo(url, forceLoad: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -153,7 +354,7 @@
|
|||||||
{
|
{
|
||||||
try
|
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))
|
if (!string.IsNullOrWhiteSpace(userResponse.Token))
|
||||||
{
|
{
|
||||||
// ذخیره توکن جدید در localStorage
|
// ذخیره توکن جدید در localStorage
|
||||||
|
|||||||
@@ -4,13 +4,10 @@
|
|||||||
|
|
||||||
<MudContainer MaxWidth="MaxWidth.Medium" Class="py-6">
|
<MudContainer MaxWidth="MaxWidth.Medium" Class="py-6">
|
||||||
<MudStack Spacing="3">
|
<MudStack Spacing="3">
|
||||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
<PageHeader Title="اطلاعات شخصی" BackHref="@RouteConstants.Profile.Index" />
|
||||||
<MudText Typo="Typo.h5">اطلاعات شخصی</MudText>
|
|
||||||
<MudButton Variant="Variant.Text" StartIcon="@Icons.Material.Filled.ArrowBack" Href="@RouteConstants.Profile.Index">بازگشت</MudButton>
|
|
||||||
</MudStack>
|
|
||||||
|
|
||||||
<MudPaper Elevation="2" Class="pa-4 rounded-lg">
|
<MudPaper Elevation="2" Class="pa-4 rounded-lg">
|
||||||
<MudForm @ref="_personalForm" Model="_updateUserRequest" Validation="@(_personalValidator.ValidateValue)">
|
<MudForm @ref="_personalForm" Model="_updateUserRequest">
|
||||||
<MudGrid Spacing="3">
|
<MudGrid Spacing="3">
|
||||||
<MudItem xs="12" md="6">
|
<MudItem xs="12" md="6">
|
||||||
<MudTextField @bind-Value="_updateUserRequest.FirstName"
|
<MudTextField @bind-Value="_updateUserRequest.FirstName"
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
using FluentValidation;
|
using CMSMicroservice.Protobuf.Protos.User;
|
||||||
using FrontOffice.BFF.User.Protobuf.Protos.User;
|
|
||||||
using FrontOffice.BFF.User.Protobuf.Validator;
|
|
||||||
using FrontOffice.Main.Utilities;
|
using FrontOffice.Main.Utilities;
|
||||||
using Mapster;
|
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
using MudBlazor;
|
using MudBlazor;
|
||||||
using Severity = MudBlazor.Severity;
|
using Severity = MudBlazor.Severity;
|
||||||
@@ -13,9 +11,8 @@ public partial class Personal : ComponentBase
|
|||||||
{
|
{
|
||||||
[Inject] private UserContract.UserContractClient UserContract { get; set; } = default!;
|
[Inject] private UserContract.UserContractClient UserContract { get; set; } = default!;
|
||||||
|
|
||||||
private GetUserResponse _userProfile = new();
|
private GetUserForCustomerResponse _userProfile = new();
|
||||||
private UpdateUserRequest _updateUserRequest = new();
|
private UpdateCustomerProfileRequest _updateUserRequest = new();
|
||||||
private readonly UpdateUserRequestValidator _personalValidator = new();
|
|
||||||
|
|
||||||
private MudForm? _personalForm;
|
private MudForm? _personalForm;
|
||||||
private DateTime? _date;
|
private DateTime? _date;
|
||||||
@@ -30,8 +27,14 @@ public partial class Personal : ComponentBase
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_userProfile = await UserContract.GetUserAsync(new());
|
_userProfile = await UserContract.GetUserForCustomerAsync(new());
|
||||||
_updateUserRequest = _userProfile.Adapt<UpdateUserRequest>();
|
_updateUserRequest = new UpdateCustomerProfileRequest
|
||||||
|
{
|
||||||
|
FirstName = _userProfile.FirstName,
|
||||||
|
LastName = _userProfile.LastName,
|
||||||
|
Email = _userProfile.Email,
|
||||||
|
NationalCode = _userProfile.NationalCode,
|
||||||
|
};
|
||||||
if (_userProfile.BirthDate != null)
|
if (_userProfile.BirthDate != null)
|
||||||
_date = _userProfile.BirthDate.ToDateTime();
|
_date = _userProfile.BirthDate.ToDateTime();
|
||||||
}
|
}
|
||||||
@@ -56,7 +59,7 @@ public partial class Personal : ComponentBase
|
|||||||
if (_date != null)
|
if (_date != null)
|
||||||
_updateUserRequest.BirthDate = _date.Value.DateTimeToTimestamp();
|
_updateUserRequest.BirthDate = _date.Value.DateTimeToTimestamp();
|
||||||
|
|
||||||
await UserContract.UpdateUserAsync(_updateUserRequest);
|
await UserContract.UpdateCustomerProfileAsync(_updateUserRequest);
|
||||||
Snackbar.Add("اطلاعات شخصی با موفقیت ذخیره شد.", Severity.Success);
|
Snackbar.Add("اطلاعات شخصی با موفقیت ذخیره شد.", Severity.Success);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|||||||
@@ -4,10 +4,7 @@
|
|||||||
|
|
||||||
<MudContainer MaxWidth="MaxWidth.Medium" Class="py-6">
|
<MudContainer MaxWidth="MaxWidth.Medium" Class="py-6">
|
||||||
<MudStack Spacing="3">
|
<MudStack Spacing="3">
|
||||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
<PageHeader Title="تنظیمات حساب" BackHref="@RouteConstants.Profile.Index" />
|
||||||
<MudText Typo="Typo.h5">تنظیمات حساب</MudText>
|
|
||||||
<MudButton Variant="Variant.Text" StartIcon="@Icons.Material.Filled.ArrowBack" Href="@RouteConstants.Profile.Index">بازگشت</MudButton>
|
|
||||||
</MudStack>
|
|
||||||
|
|
||||||
<MudPaper Elevation="2" Class="pa-4 rounded-lg">
|
<MudPaper Elevation="2" Class="pa-4 rounded-lg">
|
||||||
<MudStack Spacing="4">
|
<MudStack Spacing="4">
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using FrontOffice.BFF.User.Protobuf.Protos.User;
|
using CMSMicroservice.Protobuf.Protos.User;
|
||||||
using Mapster;
|
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
using MudBlazor;
|
using MudBlazor;
|
||||||
|
|
||||||
@@ -16,8 +15,18 @@ public partial class Settings : ComponentBase
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var user = await UserContract.GetUserAsync(new());
|
var user = await UserContract.GetUserForCustomerAsync(new());
|
||||||
_request = user.Adapt<UpdateUserRequest>();
|
_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
|
catch
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,14 +1,11 @@
|
|||||||
@attribute [Route(RouteConstants.Profile.Tree)]
|
@attribute [Route(RouteConstants.Profile.Tree)]
|
||||||
@using FrontOffice.Main.Pages.Profile.Components
|
@using FrontOffice.Main.Pages.Profile.Components
|
||||||
|
|
||||||
<PageTitle>شجرهنامه</PageTitle>
|
<PageTitle>سازمان فروش</PageTitle>
|
||||||
|
|
||||||
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
||||||
<MudStack Spacing="3">
|
<MudStack Spacing="3">
|
||||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
<PageHeader Title="سازمان فروش" BackHref="@RouteConstants.Profile.Index" />
|
||||||
<MudText Typo="Typo.h5">شجرهنامه</MudText>
|
|
||||||
<MudButton Variant="Variant.Text" StartIcon="@Icons.Material.Filled.ArrowBack" Href="@RouteConstants.Profile.Index">بازگشت</MudButton>
|
|
||||||
</MudStack>
|
|
||||||
|
|
||||||
<MudPaper Elevation="2" Class="pa-4 rounded-lg">
|
<MudPaper Elevation="2" Class="pa-4 rounded-lg">
|
||||||
<OrganizationChart />
|
<OrganizationChart />
|
||||||
|
|||||||
@@ -4,28 +4,25 @@
|
|||||||
|
|
||||||
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
||||||
<MudStack Spacing="3">
|
<MudStack Spacing="3">
|
||||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
<PageHeader Title="کیف پول" BackHref="@RouteConstants.Profile.Index" />
|
||||||
<MudText Typo="Typo.h5">کیف پول</MudText>
|
|
||||||
<MudButton Variant="Variant.Text" StartIcon="@Icons.Material.Filled.ArrowBack" Href="@RouteConstants.Profile.Index">بازگشت</MudButton>
|
|
||||||
</MudStack>
|
|
||||||
|
|
||||||
<MudGrid Spacing="2">
|
<MudGrid Spacing="2">
|
||||||
<MudItem xs="12" sm="6" md="4">
|
<MudItem xs="12" sm="6" md="4">
|
||||||
<MudPaper Elevation="2" Class="pa-4 rounded-lg">
|
<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>
|
<MudText Typo="Typo.h4" Color="Color.Primary">@_balances.Credit</MudText>
|
||||||
</MudPaper>
|
</MudPaper>
|
||||||
</MudItem>
|
</MudItem>
|
||||||
<MudItem xs="12" sm="6" md="4">
|
<MudItem xs="12" sm="6" md="4">
|
||||||
<MudPaper Elevation="2" Class="pa-4 rounded-lg">
|
<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.Warning">@_balances.Discount</MudText>
|
<MudText Typo="Typo.h4" Color="Color.Success">@_balances.Network</MudText>
|
||||||
</MudPaper>
|
</MudPaper>
|
||||||
</MudItem>
|
</MudItem>
|
||||||
<MudItem xs="12" sm="6" md="4">
|
<MudItem xs="12" sm="6" md="4">
|
||||||
<MudPaper Elevation="2" Class="pa-4 rounded-lg">
|
<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.Success">@_balances.Network</MudText>
|
<MudText Typo="Typo.h4" Color="Color.Warning">@_balances.Discount</MudText>
|
||||||
</MudPaper>
|
</MudPaper>
|
||||||
</MudItem>
|
</MudItem>
|
||||||
</MudGrid>
|
</MudGrid>
|
||||||
@@ -41,6 +38,104 @@
|
|||||||
درخواستهای برداشت
|
درخواستهای برداشت
|
||||||
</MudButton>
|
</MudButton>
|
||||||
|
|
||||||
|
<!-- دکمه شارژ کیفپول اصلی / جادویی -->
|
||||||
|
@if (_isClubMemberActive)
|
||||||
|
{
|
||||||
|
@if (_isMagicWallet)
|
||||||
|
{
|
||||||
|
<MudButton Variant="Variant.Filled"
|
||||||
|
Color="Color.Secondary"
|
||||||
|
Size="Size.Large"
|
||||||
|
FullWidth="true"
|
||||||
|
StartIcon="@Icons.Material.Filled.AutoAwesome"
|
||||||
|
Href="@RouteConstants.Profile.MagicWallet"
|
||||||
|
Class="rounded-lg">
|
||||||
|
شارژ کیفپول جادویی
|
||||||
|
</MudButton>
|
||||||
|
@if (_magicCeilingFull)
|
||||||
|
{
|
||||||
|
<MudAlert Severity="Severity.Warning" Dense="true" Variant="Variant.Outlined">
|
||||||
|
سقف شارژ جادویی این دور پر است. در صورت نیاز میتوانید بهصورت استثنایی بدون ضریب شارژ کنید.
|
||||||
|
</MudAlert>
|
||||||
|
<MudButton Variant="Variant.Outlined"
|
||||||
|
Color="Color.Primary"
|
||||||
|
Size="Size.Large"
|
||||||
|
FullWidth="true"
|
||||||
|
StartIcon="@Icons.Material.Filled.AccountBalanceWallet"
|
||||||
|
Href="@RouteConstants.Profile.ChargeCreditWallet"
|
||||||
|
Class="rounded-lg">
|
||||||
|
شارژ عادی بدون ضریب
|
||||||
|
</MudButton>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudAlert Severity="Severity.Info" Dense="true" Variant="Variant.Outlined">
|
||||||
|
کیف پول شما در حالت جادویی است؛ شارژ از مسیر جادویی با ضریب چندبرابر اعمال میشود.
|
||||||
|
</MudAlert>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudButton Variant="Variant.Filled"
|
||||||
|
Color="Color.Primary"
|
||||||
|
Size="Size.Large"
|
||||||
|
FullWidth="true"
|
||||||
|
StartIcon="@Icons.Material.Filled.AccountBalanceWallet"
|
||||||
|
Href="@RouteConstants.Profile.ChargeCreditWallet"
|
||||||
|
Class="rounded-lg">
|
||||||
|
شارژ کیفپول اصلی
|
||||||
|
</MudButton>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudPaper Elevation="0" Class="pa-3 rounded-lg" Outlined="true">
|
||||||
|
<MudStack Spacing="2">
|
||||||
|
<MudButton Variant="Variant.Filled"
|
||||||
|
Color="Color.Primary"
|
||||||
|
Size="Size.Large"
|
||||||
|
FullWidth="true"
|
||||||
|
StartIcon="@Icons.Material.Filled.AccountBalanceWallet"
|
||||||
|
Disabled="true"
|
||||||
|
Class="rounded-lg">
|
||||||
|
شارژ کیفپول اصلی
|
||||||
|
</MudButton>
|
||||||
|
<MudAlert Severity="Severity.Warning" Dense="true" Variant="Variant.Outlined">
|
||||||
|
برای شارژ کیف پول اصلی ابتدا باید پکیج را خریداری کرده و قرارداد باشگاه مشتریان را امضا کنید.
|
||||||
|
</MudAlert>
|
||||||
|
<MudStack Row="true" Spacing="1" Class="flex-wrap">
|
||||||
|
@if (!_hasPurchasedPackage)
|
||||||
|
{
|
||||||
|
<MudButton Size="Size.Small" Variant="Variant.Outlined" Color="Color.Primary"
|
||||||
|
Href="@RouteConstants.Package.List"
|
||||||
|
StartIcon="@Icons.Material.Filled.CardGiftcard">
|
||||||
|
مشاهده پکیجها
|
||||||
|
</MudButton>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudButton Size="Size.Small" Variant="Variant.Outlined" Color="Color.Primary"
|
||||||
|
Href="@RouteConstants.Club.Membership"
|
||||||
|
StartIcon="@Icons.Material.Filled.Handshake">
|
||||||
|
امضای قرارداد باشگاه
|
||||||
|
</MudButton>
|
||||||
|
}
|
||||||
|
</MudStack>
|
||||||
|
</MudStack>
|
||||||
|
</MudPaper>
|
||||||
|
}
|
||||||
|
|
||||||
|
<!-- دکمه شارژ کیفپول اعتباری -->
|
||||||
|
<MudButton Variant="Variant.Filled"
|
||||||
|
Color="Color.Info"
|
||||||
|
Size="Size.Large"
|
||||||
|
FullWidth="true"
|
||||||
|
StartIcon="@Icons.Material.Filled.CreditCard"
|
||||||
|
Href="@RouteConstants.Profile.ChargeDiscountWallet"
|
||||||
|
Class="rounded-lg">
|
||||||
|
شارژ کیفپول اعتباری
|
||||||
|
</MudButton>
|
||||||
|
|
||||||
<MudPaper Elevation="2" Class="pa-4 rounded-lg">
|
<MudPaper Elevation="2" Class="pa-4 rounded-lg">
|
||||||
<MudText Typo="Typo.h6" Class="mb-2">تراکنشها و مسیرهای شارژ</MudText>
|
<MudText Typo="Typo.h6" Class="mb-2">تراکنشها و مسیرهای شارژ</MudText>
|
||||||
|
|
||||||
@@ -71,18 +166,45 @@
|
|||||||
</MudGrid>
|
</MudGrid>
|
||||||
|
|
||||||
<MudHidden Breakpoint="Breakpoint.MdAndUp" Invert="true">
|
<MudHidden Breakpoint="Breakpoint.MdAndUp" Invert="true">
|
||||||
<MudTable Items="_txs" Dense="true">
|
<MudTable Items="_txs" Dense="true" Hover="true" Striped="true">
|
||||||
<HeaderContent>
|
<HeaderContent>
|
||||||
<MudTh>تاریخ</MudTh>
|
<MudTh>تاریخ</MudTh>
|
||||||
<MudTh>مبلغ</MudTh>
|
<MudTh>اصلی (تغییرات / مانده)</MudTh>
|
||||||
<MudTh>مسیر</MudTh>
|
<MudTh>پاداش های دریافتی (تغییرات / مانده)</MudTh>
|
||||||
|
<MudTh>اعتباری (تغییرات / مانده)</MudTh>
|
||||||
|
<MudTh>شناسه ارجاع</MudTh>
|
||||||
<MudTh>توضیحات</MudTh>
|
<MudTh>توضیحات</MudTh>
|
||||||
</HeaderContent>
|
</HeaderContent>
|
||||||
<RowTemplate>
|
<RowTemplate>
|
||||||
<MudTd>@context.Date</MudTd>
|
<MudTd DataLabel="تاریخ">
|
||||||
<MudTd Color="@(context.Amount > 0 ? Color.Success : Color.Error)">@FormatPrice(context.Amount)</MudTd>
|
<MudText Typo="Typo.caption">@context.Date</MudText>
|
||||||
<MudTd>@context.Channel</MudTd>
|
</MudTd>
|
||||||
<MudTd>@context.Description</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>
|
</RowTemplate>
|
||||||
</MudTable>
|
</MudTable>
|
||||||
</MudHidden>
|
</MudHidden>
|
||||||
@@ -91,13 +213,50 @@
|
|||||||
<MudStack Spacing="2">
|
<MudStack Spacing="2">
|
||||||
@foreach (var tx in _txs)
|
@foreach (var tx in _txs)
|
||||||
{
|
{
|
||||||
<MudPaper Class="pa-3 rounded-lg" Outlined="true">
|
<MudPaper Class="pa-3 rounded-lg" Outlined="true" Elevation="1">
|
||||||
<MudStack Spacing="1">
|
<MudStack Spacing="2">
|
||||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
||||||
<MudText>@tx.Date</MudText>
|
<MudText Typo="Typo.caption" Class="mud-text-secondary">@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.Channel</MudText>
|
||||||
</MudStack>
|
</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>
|
<MudText Typo="Typo.body2">@tx.Description</MudText>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
</MudPaper>
|
</MudPaper>
|
||||||
|
|||||||
@@ -6,13 +6,43 @@ namespace FrontOffice.Main.Pages.Profile;
|
|||||||
|
|
||||||
public partial class Wallet : ComponentBase
|
public partial class Wallet : ComponentBase
|
||||||
{
|
{
|
||||||
|
[Inject] private AuthService AuthService { get; set; } = default!;
|
||||||
|
|
||||||
private (string Credit, string Discount, string Network) _balances = ("-", "-", "-");
|
private (string Credit, string Discount, string Network) _balances = ("-", "-", "-");
|
||||||
private List<WalletTransaction> _txs = new();
|
private List<WalletTransaction> _txs = new();
|
||||||
private string? _filterReferenceId;
|
private string? _filterReferenceId;
|
||||||
private string _filterType = "all";
|
private string _filterType = "all";
|
||||||
|
private bool _isClubMemberActive;
|
||||||
|
private bool _hasPurchasedPackage;
|
||||||
|
private bool _isMagicWallet;
|
||||||
|
private bool _magicCeilingFull;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var userInfo = await AuthService.GetUserAuthInfo();
|
||||||
|
_isClubMemberActive = userInfo.IsClubMemberActive;
|
||||||
|
_hasPurchasedPackage = userInfo.HasPurchasedPackage;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
_isClubMemberActive = false;
|
||||||
|
_hasPurchasedPackage = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var magicStatus = await WalletService.GetMagicWalletStatusAsync();
|
||||||
|
_isMagicWallet = magicStatus.WalletMode == 1;
|
||||||
|
_magicCeilingFull = _isMagicWallet && magicStatus.MagicRemainingDeposit <= 0;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
_isMagicWallet = false;
|
||||||
|
_magicCeilingFull = false;
|
||||||
|
}
|
||||||
|
|
||||||
var b = await WalletService.GetBalancesAsync();
|
var b = await WalletService.GetBalancesAsync();
|
||||||
_balances = (FormatPrice(b.CreditBalance), FormatPrice(b.DiscountBalance), FormatPrice(b.NetworkBalance));
|
_balances = (FormatPrice(b.CreditBalance), FormatPrice(b.DiscountBalance), FormatPrice(b.NetworkBalance));
|
||||||
_txs = await WalletService.GetTransactionsAsync();
|
_txs = await WalletService.GetTransactionsAsync();
|
||||||
|
|||||||
@@ -4,10 +4,7 @@
|
|||||||
|
|
||||||
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
||||||
<MudStack Spacing="3">
|
<MudStack Spacing="3">
|
||||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
<PageHeader Title="درخواستهای برداشت" BackHref="@RouteConstants.Profile.Wallet" />
|
||||||
<MudText Typo="Typo.h5">درخواستهای برداشت</MudText>
|
|
||||||
<MudButton Variant="Variant.Text" StartIcon="@Icons.Material.Filled.ArrowBack" Href="@RouteConstants.Profile.Wallet">بازگشت به کیف پول</MudButton>
|
|
||||||
</MudStack>
|
|
||||||
|
|
||||||
<!-- فرم درخواست برداشت جدید -->
|
<!-- فرم درخواست برداشت جدید -->
|
||||||
<MudPaper Elevation="2" Class="pa-4 rounded-lg">
|
<MudPaper Elevation="2" Class="pa-4 rounded-lg">
|
||||||
@@ -100,7 +97,7 @@
|
|||||||
|
|
||||||
@if (_isLoading)
|
@if (_isLoading)
|
||||||
{
|
{
|
||||||
<MudProgressLinear Color="Color.Primary" Indeterminate="true" />
|
<LoadingState />
|
||||||
}
|
}
|
||||||
else if (!_withdrawals.Any())
|
else if (!_withdrawals.Any())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -58,8 +58,19 @@ public partial class WithdrawalRequests : ComponentBase
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
_isSubmittingWithdrawal = true;
|
_isSubmittingWithdrawal = true;
|
||||||
_withdrawIban=_withdrawIban!.Trim().ToUpper().Replace("IR", "").Replace(" ", "");
|
|
||||||
await WalletService.RequestWithdrawalAsync(_selectedPayout.Id, _withdrawMethod, "IR"+_withdrawIban);
|
string? normalizedIban = null;
|
||||||
|
if (_withdrawMethod == WithdrawalMethodClient.Cash)
|
||||||
|
{
|
||||||
|
normalizedIban = NormalizeIranianIban(_withdrawIban);
|
||||||
|
if (normalizedIban is null)
|
||||||
|
{
|
||||||
|
Snackbar.Add("فرمت شماره شبا معتبر نیست. باید IR و ۲۴ رقم باشد (خط تیره اختیاری است).", Severity.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await WalletService.RequestWithdrawalAsync(_selectedPayout.Id, _withdrawMethod, normalizedIban);
|
||||||
Snackbar.Add("درخواست برداشت ثبت شد.", Severity.Success);
|
Snackbar.Add("درخواست برداشت ثبت شد.", Severity.Success);
|
||||||
|
|
||||||
// بروزرسانی لیست
|
// بروزرسانی لیست
|
||||||
@@ -125,4 +136,26 @@ public partial class WithdrawalRequests : ComponentBase
|
|||||||
1 => "الماس",
|
1 => "الماس",
|
||||||
_ => "-"
|
_ => "-"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// فاصله و خط تیره را حذف میکند و شبا را به شکل IR + ۲۴ رقم برمیگرداند.
|
||||||
|
/// </summary>
|
||||||
|
private static string? NormalizeIranianIban(string? iban)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(iban))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var normalized = iban.Trim().ToUpperInvariant()
|
||||||
|
.Replace(" ", "", StringComparison.Ordinal)
|
||||||
|
.Replace("-", "", StringComparison.Ordinal);
|
||||||
|
|
||||||
|
if (normalized.StartsWith("IR", StringComparison.Ordinal))
|
||||||
|
normalized = normalized[2..];
|
||||||
|
|
||||||
|
normalized = "IR" + normalized;
|
||||||
|
|
||||||
|
return System.Text.RegularExpressions.Regex.IsMatch(normalized, @"^IR\d{24}$")
|
||||||
|
? normalized
|
||||||
|
: null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,10 +6,11 @@
|
|||||||
<MudContainer MaxWidth="MaxWidth.Large" Class="px-1">
|
<MudContainer MaxWidth="MaxWidth.Large" Class="px-1">
|
||||||
<MudGrid Spacing="4" Justify="Justify.Center">
|
<MudGrid Spacing="4" Justify="Justify.Center">
|
||||||
<MudItem xs="12" md="5" Class="d-none d-sm-block">
|
<MudItem xs="12" md="5" Class="d-none d-sm-block">
|
||||||
<MudStack Spacing="2" Class="mb-6">
|
<MudStack Spacing="3" Class="mb-6">
|
||||||
<MudChip T="string" Color="Color.Secondary" Variant="Variant.Filled" Class="mb-2">ثبتنام سه
|
<MudChip T="string" Color="Color.Primary" Variant="Variant.Filled" Size="Size.Small" Class="rounded-pill">
|
||||||
مرحلهای</MudChip>
|
ثبتنام سه مرحلهای
|
||||||
<MudText Typo="Typo.h3" Class="mb-2">
|
</MudChip>
|
||||||
|
<MudText Typo="Typo.h3" Class="mb-1">
|
||||||
فقط در چند دقیقه حساب خود را فعال کنید
|
فقط در چند دقیقه حساب خود را فعال کنید
|
||||||
</MudText>
|
</MudText>
|
||||||
<MudText Typo="Typo.body1" Class="mud-text-secondary">
|
<MudText Typo="Typo.body1" Class="mud-text-secondary">
|
||||||
@@ -18,34 +19,47 @@
|
|||||||
</MudText>
|
</MudText>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
|
|
||||||
<MudPaper Elevation="1" Class="pa-4 rounded-xl gradient-border">
|
<MudPaper Elevation="0" Class="pa-5 rounded-xl" Style="border:1px solid var(--mud-palette-divider);">
|
||||||
<MudStack Spacing="2">
|
<MudStack Spacing="3">
|
||||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
|
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
|
||||||
<MudIcon Icon="@Icons.Material.Filled.VerifiedUser" Color="Color.Primary" />
|
<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>
|
<MudText Typo="Typo.subtitle1">مزایای ثبتنام آنلاین</MudText>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
<MudText Typo="Typo.body2" Class="mud-text-secondary">
|
<MudStack Spacing="2">
|
||||||
• تایید سریع و آنلاین شماره موبایل<br />
|
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
|
||||||
• تکمیل اطلاعات هویتی بدون مراجعه حضوری<br />
|
<MudIcon Icon="@Icons.Material.Outlined.PhoneAndroid" Size="Size.Small" Color="Color.Primary" />
|
||||||
• دریافت نسخهی دیجیتال قرارداد برای بررسی دقیق
|
<MudText Typo="Typo.body2" Class="mud-text-secondary">تایید سریع و آنلاین شماره موبایل</MudText>
|
||||||
</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>
|
</MudStack>
|
||||||
</MudPaper>
|
</MudPaper>
|
||||||
</MudItem>
|
</MudItem>
|
||||||
|
|
||||||
<MudItem xs="12" md="7">
|
<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)
|
@if (_completed)
|
||||||
{
|
{
|
||||||
<MudStack Spacing="3" AlignItems="AlignItems.Center" Class="text-center">
|
<MudStack Spacing="3" AlignItems="AlignItems.Center" Class="text-center py-6">
|
||||||
<MudAvatar Icon="@Icons.Material.Filled.CheckCircle" Color="Color.Success" Size="Size.Large" />
|
<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.h4">درخواست شما ثبت شد</MudText>
|
||||||
<MudText Typo="Typo.body1" Class="mud-text-secondary">
|
<MudText Typo="Typo.body1" Class="mud-text-secondary" Style="max-width:400px;">
|
||||||
تیم ما پس از بررسی اطلاعات با شما تماس خواهد گرفت. میتوانید از طریق داشبورد وضعیت ثبتنام
|
تیم ما پس از بررسی اطلاعات با شما تماس خواهد گرفت. میتوانید از طریق داشبورد وضعیت ثبتنام
|
||||||
را دنبال کنید.
|
را دنبال کنید.
|
||||||
</MudText>
|
</MudText>
|
||||||
<MudStack Row="true" Spacing="2" Justify="Justify.Center">
|
<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))">بازگشت به صفحه
|
OnClick="@(() => Navigation.NavigateTo(RouteConstants.Main.MainPage))">بازگشت به صفحه
|
||||||
اصلی</MudButton>
|
اصلی</MudButton>
|
||||||
<MudButton Variant="Variant.Outlined" Color="Color.Primary"
|
<MudButton Variant="Variant.Outlined" Color="Color.Primary"
|
||||||
@@ -56,43 +70,45 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<MudStack Spacing="3" >
|
<MudStack Spacing="3">
|
||||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
||||||
<MudText Typo="Typo.h4">مراحل ثبتنام</MudText>
|
<MudText Typo="Typo.h5">مراحل ثبتنام</MudText>
|
||||||
<MudChip T="string" Color="Color.Info" Variant="Variant.Outlined" Size="Size.Small">۳ مرحله
|
<MudChip T="string" Color="Color.Primary" Variant="Variant.Outlined" Size="Size.Small" Class="rounded-pill">
|
||||||
|
مرحله @(_activeStep + 1) از ۳
|
||||||
</MudChip>
|
</MudChip>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
|
|
||||||
@if (_isSubmitting)
|
@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>
|
<ChildContent>
|
||||||
<MudStep Label="تأیید موبایل" Icon="@Icons.Material.Filled.Smartphone">
|
<MudStep Label="تأیید موبایل" Icon="@Icons.Material.Outlined.PhoneAndroid">
|
||||||
@* Inline AuthDialog with captcha enabled *@
|
@* Inline AuthDialog with captcha enabled *@
|
||||||
<AuthDialog @ref="_authDialog" InlineMode="true" HideCancelButton="true" OnLoginSuccess="@(async () => { OnPhoneVerified(); })" />
|
<AuthDialog @ref="_authDialog" InlineMode="true" HideCancelButton="true" OnLoginSuccess="@(async () => { OnPhoneVerified(); })" />
|
||||||
</MudStep>
|
</MudStep>
|
||||||
|
|
||||||
<MudStep Label="اطلاعات هویتی" Icon="@Icons.Material.Filled.Badge">
|
<MudStep Label="اطلاعات هویتی" Icon="@Icons.Material.Outlined.Badge">
|
||||||
<MudForm @ref="_stepTwoForm">
|
<MudForm @ref="_stepTwoForm">
|
||||||
<MudTextField Label="نام" Variant="Variant.Outlined" Immediate="true"
|
<MudStack Spacing="3">
|
||||||
@bind-Value="_model.FirstName" For="@(() => _model.FirstName)" />
|
<MudTextField Label="نام" Variant="Variant.Outlined" Immediate="true"
|
||||||
<MudTextField Label="نام خانوادگی" Variant="Variant.Outlined" Immediate="true"
|
@bind-Value="_model.FirstName" For="@(() => _model.FirstName)" />
|
||||||
@bind-Value="_model.LastName" For="@(() => _model.LastName)" />
|
<MudTextField Label="نام خانوادگی" Variant="Variant.Outlined" Immediate="true"
|
||||||
<MudTextField Label="کد ملی" Variant="Variant.Outlined" Immediate="true"
|
@bind-Value="_model.LastName" For="@(() => _model.LastName)" />
|
||||||
MaxLength="10" @bind-Value="_model.NationalCode"
|
<MudTextField Label="کد ملی" Variant="Variant.Outlined" Immediate="true"
|
||||||
For="@(() => _model.NationalCode)" InputType="InputType.Number" />
|
MaxLength="10" @bind-Value="_model.NationalCode"
|
||||||
|
For="@(() => _model.NationalCode)" InputType="InputType.Number" />
|
||||||
|
</MudStack>
|
||||||
</MudForm>
|
</MudForm>
|
||||||
</MudStep>
|
</MudStep>
|
||||||
|
|
||||||
<MudStep Label="قوانین و قرارداد" Icon="@Icons.Material.Filled.Rule">
|
<MudStep Label="قوانین و قرارداد" Icon="@Icons.Material.Outlined.Rule">
|
||||||
<MudForm @ref="_stepThreeForm">
|
<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>
|
</MudAlert>
|
||||||
<MudPaper Elevation="0" Class="terms-box pa-4 mb-3">
|
<MudPaper Elevation="0" Class="terms-box pa-4 mb-3">
|
||||||
<MudText Typo="Typo.subtitle2" Class="mb-2">بخشی از قوانین:</MudText>
|
<MudText Typo="Typo.subtitle2" Class="mb-2">بخشی از قوانین:</MudText>
|
||||||
@@ -106,47 +122,44 @@
|
|||||||
</MudList>
|
</MudList>
|
||||||
</MudPaper>
|
</MudPaper>
|
||||||
|
|
||||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2" Class="mb-2">
|
<MudButton Variant="Variant.Outlined" Color="Color.Primary" Class="rounded-lg mb-3"
|
||||||
<MudButton Variant="Variant.Outlined" Color="Color.Primary"
|
StartIcon="@Icons.Material.Outlined.Download" Disabled="_isSubmitting"
|
||||||
StartIcon="@Icons.Material.Filled.Download" Disabled="_isSubmitting"
|
OnClick="DownloadContract">
|
||||||
OnClick="DownloadContract">
|
دانلود قرارداد نمونه (PDF)
|
||||||
دانلود/پرینت قرارداد نمونه
|
</MudButton>
|
||||||
</MudButton>
|
|
||||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">فرمت: PDF</MudText>
|
|
||||||
</MudStack>
|
|
||||||
|
|
||||||
<MudCheckBox @bind-Checked="_model.AcceptTerms" Color="Color.Success"
|
<MudCheckBox @bind-Checked="_model.AcceptTerms" Color="Color.Primary"
|
||||||
For="@(() => _model.AcceptTerms)"
|
For="@(() => _model.AcceptTerms)"
|
||||||
Label="قوانین و مقررات را مطالعه کردهام و میپذیرم" />
|
Label="قوانین و مقررات را مطالعه کردهام و میپذیرم" />
|
||||||
</MudForm>
|
</MudForm>
|
||||||
</MudStep>
|
</MudStep>
|
||||||
</ChildContent>
|
</ChildContent>
|
||||||
<ActionContent Context="stepper">
|
<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)
|
@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>
|
OnClick="@AuthService.LogoutAsync">خروج از حساب</MudButton>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
<MudButton Variant="Variant.Text" Color="Color.Secondary" Size="Size.Small"
|
||||||
<MudButton Variant="Variant.Text" Color="Color.Secondary"
|
Disabled="_activeStep == 0 || _isSubmitting"
|
||||||
Disabled="_activeStep == 0 || _isSubmitting"
|
OnClick="@(async () => { await GoBack(); })">مرحله قبل</MudButton>
|
||||||
OnClick="@(async () => { await GoBack(); })">مرحله قبل</MudButton>
|
|
||||||
}
|
}
|
||||||
<MudSpacer/>
|
<MudSpacer/>
|
||||||
<MudButton Variant="Variant.Filled" Color="Color.Primary"
|
<MudButton Variant="Variant.Filled" Color="Color.Primary" Class="rounded-lg"
|
||||||
Disabled="_isSubmitting"
|
Disabled="_isSubmitting"
|
||||||
OnClick="@(async () => { await GoNextAsync(); })">
|
OnClick="@(async () => { await GoNextAsync(); })">
|
||||||
|
@if (_isSubmitting)
|
||||||
|
{
|
||||||
|
<MudProgressCircular Size="Size.Small" Indeterminate="true" Class="me-2" />
|
||||||
|
}
|
||||||
@_nextButtonText
|
@_nextButtonText
|
||||||
</MudButton>
|
</MudButton>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
</ActionContent>
|
</ActionContent>
|
||||||
</MudStepper>
|
</MudStepper>
|
||||||
|
|
||||||
|
|
||||||
</MudStack>
|
</MudStack>
|
||||||
}
|
}
|
||||||
</MudPaper>
|
</MudPaper>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using DateTimeConverterCL;
|
using DateTimeConverterCL;
|
||||||
using FrontOffice.BFF.User.Protobuf.Protos.User;
|
using CMSMicroservice.Protobuf.Protos.User;
|
||||||
using FrontOffice.Main.Shared;
|
using FrontOffice.Main.Shared;
|
||||||
using FrontOffice.Main.Utilities;
|
using FrontOffice.Main.Utilities;
|
||||||
using Google.Protobuf.WellKnownTypes;
|
using Google.Protobuf.WellKnownTypes;
|
||||||
@@ -23,13 +23,14 @@ public partial class RegisterWizard
|
|||||||
private bool _completed;
|
private bool _completed;
|
||||||
private AuthDialog? _authDialog;
|
private AuthDialog? _authDialog;
|
||||||
private MudStepper? _mudStepper;
|
private MudStepper? _mudStepper;
|
||||||
private UpdateUserRequest _updateUserRequest = new();
|
private UpdateCustomerProfileRequest _updateUserRequest = new();
|
||||||
private bool _isAuthenticated;
|
private bool _isAuthenticated;
|
||||||
private CancellationTokenSource? _operationCts;
|
private CancellationTokenSource? _operationCts;
|
||||||
private UserAuthInfo _userAuthInfo;
|
private UserAuthInfo _userAuthInfo;
|
||||||
private Guid signGuid = Guid.NewGuid();
|
private Guid signGuid = Guid.NewGuid();
|
||||||
private const string TokenStorageKey = "auth:token";
|
private const string TokenStorageKey = "auth:token";
|
||||||
private readonly DialogOptions _normalWidth = new() { MaxWidth = MaxWidth.ExtraSmall, FullWidth = true };
|
private readonly DialogOptions _normalWidth = new() { MaxWidth = MaxWidth.ExtraSmall, FullWidth = true };
|
||||||
|
private bool _initialDataLoaded;
|
||||||
|
|
||||||
[Inject] private AuthService AuthService { get; set; } = default!;
|
[Inject] private AuthService AuthService { get; set; } = default!;
|
||||||
[Inject] private IDeviceDetector _deviceDetector { get; set; } = default!;
|
[Inject] private IDeviceDetector _deviceDetector { get; set; } = default!;
|
||||||
@@ -55,6 +56,7 @@ public partial class RegisterWizard
|
|||||||
if (_userAuthInfo.IsSignMainContract)
|
if (_userAuthInfo.IsSignMainContract)
|
||||||
{
|
{
|
||||||
Navigation.NavigateTo(RouteConstants.Profile.Index);
|
Navigation.NavigateTo(RouteConstants.Profile.Index);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_activeStep == 0)
|
if (_activeStep == 0)
|
||||||
@@ -63,11 +65,12 @@ public partial class RegisterWizard
|
|||||||
_activeStep = 1;
|
_activeStep = 1;
|
||||||
await InvokeAsync(StateHasChanged);
|
await InvokeAsync(StateHasChanged);
|
||||||
}
|
}
|
||||||
else if (_activeStep == 1)
|
else if (_activeStep == 1 && !_initialDataLoaded)
|
||||||
{
|
{
|
||||||
|
_initialDataLoaded = true;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var existUser = await UserContract.GetUserAsync(new Empty());
|
var existUser = await UserContract.GetUserForCustomerAsync(new GetUserForCustomerRequest());
|
||||||
if (existUser != null && !string.IsNullOrEmpty(existUser.FirstName) &&
|
if (existUser != null && !string.IsNullOrEmpty(existUser.FirstName) &&
|
||||||
string.IsNullOrWhiteSpace(_model.FirstName))
|
string.IsNullOrWhiteSpace(_model.FirstName))
|
||||||
{
|
{
|
||||||
@@ -95,7 +98,6 @@ public partial class RegisterWizard
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
await base.OnAfterRenderAsync(firstRender);
|
await base.OnAfterRenderAsync(firstRender);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -355,7 +357,7 @@ public partial class RegisterWizard
|
|||||||
_updateUserRequest.NationalCode = _model.NationalCode.PersianToEnglish();
|
_updateUserRequest.NationalCode = _model.NationalCode.PersianToEnglish();
|
||||||
|
|
||||||
|
|
||||||
await UserContract.UpdateUserAsync(request: _updateUserRequest);
|
await UserContract.UpdateCustomerProfileAsync(request: _updateUserRequest);
|
||||||
Snackbar.Add("اطلاعات شخصی با موفقیت ذخیره شد.", Severity.Success);
|
Snackbar.Add("اطلاعات شخصی با موفقیت ذخیره شد.", Severity.Success);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
||||||
<MudStack Spacing="3">
|
<MudStack Spacing="3">
|
||||||
<MudText Typo="Typo.h4">سبد خرید</MudText>
|
<PageHeader Title="سبد خرید" BackHref="@RouteConstants.Store.Products" />
|
||||||
|
|
||||||
@if (CartData.Items.Count == 0)
|
@if (CartData.Items.Count == 0)
|
||||||
{
|
{
|
||||||
@@ -30,8 +30,8 @@
|
|||||||
<MudTd>
|
<MudTd>
|
||||||
<MudStack Spacing="1">
|
<MudStack Spacing="1">
|
||||||
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
|
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
|
||||||
<MudImage Src="@GetProductImageUrl(context.ImageUrl)" Alt="@context.Title"
|
<AppImage Path="@GetProductImageUrl(context.ImageUrl)" Alt="@context.Title"
|
||||||
Width="64" Height="64" Class="product-thumb" />
|
ImgWidth="64" ImgHeight="64" Class="rounded-lg" ObjectFit="ObjectFit.Cover" />
|
||||||
<MudText>@context.Title</MudText>
|
<MudText>@context.Title</MudText>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
@if (context.Discount > 0 || !string.IsNullOrWhiteSpace(context.Created) || !string.IsNullOrWhiteSpace(context.Description))
|
@if (context.Discount > 0 || !string.IsNullOrWhiteSpace(context.Created) || !string.IsNullOrWhiteSpace(context.Description))
|
||||||
@@ -86,8 +86,8 @@
|
|||||||
<MudStack Spacing="1">
|
<MudStack Spacing="1">
|
||||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
||||||
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
|
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
|
||||||
<MudImage Src="@GetProductImageUrl(item.ImageUrl)" Alt="@item.Title"
|
<AppImage Path="@GetProductImageUrl(item.ImageUrl)" Alt="@item.Title"
|
||||||
Width="50" Height="50" Class="rounded-circle" />
|
ImgWidth="50" ImgHeight="50" Class="rounded-lg" ObjectFit="ObjectFit.Cover" />
|
||||||
<MudText Typo="Typo.subtitle2">@item.Title</MudText>
|
<MudText Typo="Typo.subtitle2">@item.Title</MudText>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
@if (item.Discount > 0)
|
@if (item.Discount > 0)
|
||||||
@@ -154,8 +154,8 @@
|
|||||||
</MudStack>
|
</MudStack>
|
||||||
}
|
}
|
||||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
||||||
<MudText Typo="Typo.h6" Style="font-weight:bold;">مبلغ قابل پرداخت:</MudText>
|
<MudText Typo="Typo.h6" Class="fw-bold">مبلغ قابل پرداخت:</MudText>
|
||||||
<MudText Typo="Typo.h6" Color="Color.Primary" Style="font-weight:bold;">@FormatPrice(TotalWithVAT) تومان</MudText>
|
<MudText Typo="Typo.h6" Color="Color.Primary" Class="fw-bold">@FormatPrice(TotalWithVAT) تومان</MudText>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
|
|
||||||
@@ -185,8 +185,8 @@
|
|||||||
}
|
}
|
||||||
<MudDivider Class="my-1" />
|
<MudDivider Class="my-1" />
|
||||||
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||||||
<MudText Typo="Typo.subtitle1" Style="font-weight:bold;">مبلغ قابل پرداخت:</MudText>
|
<MudText Typo="Typo.subtitle1" Class="fw-bold">مبلغ قابل پرداخت:</MudText>
|
||||||
<MudText Typo="Typo.subtitle1" Color="Color.Primary" Style="font-weight:bold;">@FormatPrice(TotalWithVAT) تومان</MudText>
|
<MudText Typo="Typo.subtitle1" Color="Color.Primary" Class="fw-bold">@FormatPrice(TotalWithVAT) تومان</MudText>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
</MudPaper>
|
</MudPaper>
|
||||||
|
|||||||
@@ -8,11 +8,17 @@ public partial class Cart : ComponentBase, IDisposable
|
|||||||
{
|
{
|
||||||
[Inject] private CartService CartService { get; set; } = default!;
|
[Inject] private CartService CartService { get; set; } = default!;
|
||||||
[Inject] private VATService VAT { get; set; } = default!;
|
[Inject] private VATService VAT { get; set; } = default!;
|
||||||
|
[Inject] private AuthDialogService AuthDialogService { get; set; } = default!;
|
||||||
|
[Inject] private AuthService AuthService { get; set; } = default!;
|
||||||
// Navigation and Snackbar are available via _Imports.razor
|
// Navigation and Snackbar are available via _Imports.razor
|
||||||
private CartService CartData => CartService;
|
private CartService CartData => CartService;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
|
if (!await AuthService.IsAuthenticatedAsync())
|
||||||
|
{
|
||||||
|
await AuthDialogService.ShowAuthDialogAsync();
|
||||||
|
}
|
||||||
// لود سبد خرید (فقط اگر کاربر لاگین کرده باشد)
|
// لود سبد خرید (فقط اگر کاربر لاگین کرده باشد)
|
||||||
await CartService.EnsureInitializedAsync();
|
await CartService.EnsureInitializedAsync();
|
||||||
CartService.OnChange += StateHasChanged;
|
CartService.OnChange += StateHasChanged;
|
||||||
@@ -66,7 +72,7 @@ public partial class Cart : ComponentBase, IDisposable
|
|||||||
private long VATAmount => VAT.CalculateVAT(CartData.Total);
|
private long VATAmount => VAT.CalculateVAT(CartData.Total);
|
||||||
|
|
||||||
private static string GetProductImageUrl(string? imageUrl)
|
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()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,9 +5,9 @@
|
|||||||
<PageTitle>دستهبندیها</PageTitle>
|
<PageTitle>دستهبندیها</PageTitle>
|
||||||
|
|
||||||
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
||||||
|
<PageHeader Title="دستهبندی محصولات" BackHref="@RouteConstants.Store.Products" />
|
||||||
<MudPaper Elevation="1" Class="pa-4 mb-4 rounded-lg">
|
<MudPaper Elevation="1" Class="pa-4 mb-4 rounded-lg">
|
||||||
<MudStack Spacing="1">
|
<MudStack Spacing="1">
|
||||||
<MudText Typo="Typo.h5">دستهبندی محصولات</MudText>
|
|
||||||
<MudText Typo="Typo.body2" Class="mud-text-secondary">
|
<MudText Typo="Typo.body2" Class="mud-text-secondary">
|
||||||
از بین درخت دستهبندیها انتخاب کنید تا محصولات آن دسته را مشاهده نمایید.
|
از بین درخت دستهبندیها انتخاب کنید تا محصولات آن دسته را مشاهده نمایید.
|
||||||
</MudText>
|
</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)]
|
@attribute [Route(RouteConstants.Store.CheckoutSummary)]
|
||||||
|
|
||||||
<PageTitle>خلاصه خرید</PageTitle>
|
<PageTitle>خلاصه خرید</PageTitle>
|
||||||
|
|
||||||
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
||||||
|
<PageHeader Title="خلاصه خرید" BackHref="@RouteConstants.Store.Cart" />
|
||||||
<MudGrid Spacing="3">
|
<MudGrid Spacing="3">
|
||||||
<MudItem xs="12" md="8">
|
<MudItem xs="12" md="8">
|
||||||
<MudPaper Elevation="2" Class="pa-4 rounded-lg mb-3">
|
<MudPaper Elevation="2" Class="pa-4 rounded-lg mb-3">
|
||||||
@@ -29,7 +31,7 @@
|
|||||||
@foreach (var address in _addresses)
|
@foreach (var address in _addresses)
|
||||||
{
|
{
|
||||||
<MudPaper Outlined="@(_selectedAddress?.Id != address.Id)"
|
<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"
|
Class="pa-3 rounded-xl cursor-pointer"
|
||||||
Style="@(_selectedAddress?.Id == address.Id ? "border: 2px solid var(--mud-palette-primary);" : "")"
|
Style="@(_selectedAddress?.Id == address.Id ? "border: 2px solid var(--mud-palette-primary);" : "")"
|
||||||
@onclick="() => _selectedAddress = address">
|
@onclick="() => _selectedAddress = address">
|
||||||
@@ -83,8 +85,8 @@
|
|||||||
<MudListItemText>
|
<MudListItemText>
|
||||||
<MudStack Spacing="1">
|
<MudStack Spacing="1">
|
||||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
||||||
<MudImage Src="@GetProductImageUrl(item.ImageUrl)" Alt="@item.Title"
|
<AppImage Path="@GetProductImageUrl(item.ImageUrl)" Alt="@item.Title"
|
||||||
Width="48" Height="48" Class="rounded-circle" />
|
ImgWidth="48" ImgHeight="48" Class="rounded-lg" ObjectFit="ObjectFit.Cover" />
|
||||||
<MudText Typo="Typo.subtitle2">@item.Title</MudText>
|
<MudText Typo="Typo.subtitle2">@item.Title</MudText>
|
||||||
<MudText Typo="Typo.subtitle2">@FormatPrice(item.LineTotal)</MudText>
|
<MudText Typo="Typo.subtitle2">@FormatPrice(item.LineTotal)</MudText>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
@@ -117,8 +119,8 @@
|
|||||||
}
|
}
|
||||||
<MudDivider Class="my-1" />
|
<MudDivider Class="my-1" />
|
||||||
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||||||
<MudText Typo="Typo.subtitle1" Style="font-weight: bold;">مبلغ قابل پرداخت:</MudText>
|
<MudText Typo="Typo.subtitle1" Class="fw-bold">مبلغ قابل پرداخت:</MudText>
|
||||||
<MudText Typo="Typo.subtitle1" Color="Color.Primary" Style="font-weight: bold;">@FormatPrice(VAT.AddVAT(Cart.Total))</MudText>
|
<MudText Typo="Typo.subtitle1" Color="Color.Primary" Class="fw-bold">@FormatPrice(VAT.AddVAT(Cart.Total))</MudText>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
using FrontOffice.BFF.UserAddress.Protobuf.Protos.UserAddress;
|
using CMSMicroservice.Protobuf.Protos.UserAddress;
|
||||||
using FrontOffice.BFF.UserOrder.Protobuf.Protos.UserOrder;
|
using CMSMicroservice.Protobuf.Protos.UserOrder;
|
||||||
|
using FrontOffice.Main.Shared;
|
||||||
using FrontOffice.Main.Utilities;
|
using FrontOffice.Main.Utilities;
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
using MudBlazor;
|
using MudBlazor;
|
||||||
|
using Messages = CMSMicroservice.Protobuf.Protos;
|
||||||
|
|
||||||
namespace FrontOffice.Main.Pages.Store;
|
namespace FrontOffice.Main.Pages.Store;
|
||||||
|
|
||||||
@@ -13,22 +15,31 @@ public partial class CheckoutSummary : ComponentBase
|
|||||||
[Inject] private VATService VAT { get; set; } = default!;
|
[Inject] private VATService VAT { get; set; } = default!;
|
||||||
[Inject] private UserAddressContract.UserAddressContractClient UserAddressContract { get; set; } = default!;
|
[Inject] private UserAddressContract.UserAddressContractClient UserAddressContract { get; set; } = default!;
|
||||||
[Inject] private UserOrderContract.UserOrderContractClient UserOrderContract { get; set; } = default!;
|
[Inject] private UserOrderContract.UserOrderContractClient UserOrderContract { get; set; } = default!;
|
||||||
|
[Inject] private AuthDialogService AuthDialogService { get; set; } = default!;
|
||||||
|
[Inject] private AuthService AuthService { get; set; } = default!;
|
||||||
// Snackbar and Navigation are injected via _Imports.razor
|
// Snackbar and Navigation are injected via _Imports.razor
|
||||||
|
|
||||||
private List<GetAllUserAddressByFilterResponseModel> _addresses = new();
|
private List<CustomerAddressModel> _addresses = new();
|
||||||
private GetAllUserAddressByFilterResponseModel? _selectedAddress;
|
private CustomerAddressModel? _selectedAddress;
|
||||||
private bool _loadingAddresses;
|
private bool _loadingAddresses;
|
||||||
private long walletBalance;
|
private long walletBalance;
|
||||||
|
|
||||||
private PaymentMethod _payment = PaymentMethod.Wallet;
|
private Messages.PaymentMethod _payment = Messages.PaymentMethod.Wallet;
|
||||||
|
|
||||||
private bool CanPlaceOrder => Cart.Items.Count > 0 && _selectedAddress != null;
|
private bool CanPlaceOrder => Cart.Items.Count > 0 && _selectedAddress != null;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
// لود سبد خرید (فقط اگر کاربر لاگین کرده باشد)
|
if (!await AuthService.IsAuthenticatedAsync())
|
||||||
|
{
|
||||||
|
await AuthDialogService.ShowAuthDialogAsync();
|
||||||
|
}
|
||||||
await Cart.EnsureInitializedAsync();
|
await Cart.EnsureInitializedAsync();
|
||||||
await LoadAddresses();
|
var userInfo = await AuthService.GetUserAuthInfo();
|
||||||
|
if (userInfo.HasAddress)
|
||||||
|
await LoadAddresses();
|
||||||
|
else
|
||||||
|
_addresses = new();
|
||||||
await LoadWalletBalance();
|
await LoadWalletBalance();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,7 +47,6 @@ public partial class CheckoutSummary : ComponentBase
|
|||||||
{
|
{
|
||||||
if (firstRender)
|
if (firstRender)
|
||||||
{
|
{
|
||||||
// بارگذاری نرخ VAT
|
|
||||||
await VAT.LoadAsync();
|
await VAT.LoadAsync();
|
||||||
}
|
}
|
||||||
await base.OnAfterRenderAsync(firstRender);
|
await base.OnAfterRenderAsync(firstRender);
|
||||||
@@ -45,9 +55,7 @@ public partial class CheckoutSummary : ComponentBase
|
|||||||
private async Task LoadWalletBalance()
|
private async Task LoadWalletBalance()
|
||||||
{
|
{
|
||||||
var walletResult = await WalletService.GetBalancesAsync();
|
var walletResult = await WalletService.GetBalancesAsync();
|
||||||
walletBalance = walletResult.CreditBalance
|
walletBalance = walletResult.CreditBalance;
|
||||||
// + walletResult.NetworkBalance
|
|
||||||
;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task LoadAddresses()
|
private async Task LoadAddresses()
|
||||||
@@ -55,7 +63,7 @@ public partial class CheckoutSummary : ComponentBase
|
|||||||
_loadingAddresses = true;
|
_loadingAddresses = true;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var response = await UserAddressContract.GetAllUserAddressByFilterAsync(new());
|
var response = await UserAddressContract.GetCustomerAddressesAsync(new());
|
||||||
if (response?.Models?.Any() == true)
|
if (response?.Models?.Any() == true)
|
||||||
{
|
{
|
||||||
_addresses = response.Models.ToList();
|
_addresses = response.Models.ToList();
|
||||||
@@ -86,11 +94,15 @@ public partial class CheckoutSummary : ComponentBase
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var totalRequired = VAT.AddVAT(Cart.Total);
|
||||||
|
if (await TryHandleInsufficientBalanceAsync(totalRequired))
|
||||||
|
return;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var request = new SubmitShopBuyOrderRequest
|
var request = new SubmitShopBuyOrderRequest
|
||||||
{
|
{
|
||||||
TotalAmount = VAT.AddVAT(Cart.Total)
|
TotalAmount = totalRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
var response = await UserOrderContract.SubmitShopBuyOrderAsync(request);
|
var response = await UserOrderContract.SubmitShopBuyOrderAsync(request);
|
||||||
@@ -100,17 +112,87 @@ public partial class CheckoutSummary : ComponentBase
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
if (CreditChargeNavigation.IsInsufficientWalletBalance(ex))
|
||||||
|
{
|
||||||
|
await LoadWalletBalance();
|
||||||
|
await TryHandleInsufficientBalanceAsync(totalRequired);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Snackbar.Add($"خطا در ثبت سفارش: {ex.Message}", Severity.Error);
|
Snackbar.Add($"خطا در ثبت سفارش: {ex.Message}", Severity.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<bool> TryHandleInsufficientBalanceAsync(long totalRequired)
|
||||||
|
{
|
||||||
|
if (walletBalance >= totalRequired)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var shortfall = totalRequired - walletBalance;
|
||||||
|
var allowCharge = false;
|
||||||
|
var hasPurchasedPackage = false;
|
||||||
|
var isMagicWallet = false;
|
||||||
|
var magicCeilingFull = false;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var userInfo = await AuthService.GetUserAuthInfo();
|
||||||
|
allowCharge = userInfo.IsClubMemberActive;
|
||||||
|
hasPurchasedPackage = userInfo.HasPurchasedPackage;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
allowCharge = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var magicStatus = await WalletService.GetMagicWalletStatusAsync();
|
||||||
|
isMagicWallet = magicStatus.WalletMode == 1;
|
||||||
|
magicCeilingFull = isMagicWallet && magicStatus.MagicRemainingDeposit <= 0;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
isMagicWallet = false;
|
||||||
|
magicCeilingFull = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var parameters = new DialogParameters<InsufficientCreditDialog>
|
||||||
|
{
|
||||||
|
{ x => x.CurrentBalance, walletBalance },
|
||||||
|
{ x => x.RequiredAmount, totalRequired },
|
||||||
|
{ x => x.ShortfallAmount, shortfall },
|
||||||
|
{ x => x.AllowChargeCredit, allowCharge },
|
||||||
|
{ x => x.HasPurchasedPackage, hasPurchasedPackage },
|
||||||
|
{ x => x.IsMagicWallet, isMagicWallet },
|
||||||
|
{ x => x.MagicCeilingFull, magicCeilingFull }
|
||||||
|
};
|
||||||
|
|
||||||
|
var dialog = await DialogService.ShowAsync<InsufficientCreditDialog>(
|
||||||
|
"موجودی کافی نیست",
|
||||||
|
parameters,
|
||||||
|
new DialogOptions
|
||||||
|
{
|
||||||
|
CloseOnEscapeKey = true,
|
||||||
|
MaxWidth = MaxWidth.Small,
|
||||||
|
FullWidth = true
|
||||||
|
});
|
||||||
|
|
||||||
|
var result = await dialog.Result;
|
||||||
|
if (allowCharge && result is { Canceled: false } && result.Data is long chargeShortfall)
|
||||||
|
{
|
||||||
|
var chargeAmount = CreditChargeNavigation.NormalizeChargeAmount(chargeShortfall);
|
||||||
|
Navigation.NavigateTo(CreditChargeNavigation.BuildChargeUrl(
|
||||||
|
chargeAmount,
|
||||||
|
RouteConstants.Store.CheckoutSummary));
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private static string FormatPrice(long price) => string.Format("{0:N0} تومان", price);
|
private static string FormatPrice(long price) => string.Format("{0:N0} تومان", price);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// محاسبه مالیات بر ارزش افزوده
|
|
||||||
/// </summary>
|
|
||||||
private long CalculateVAT() => VAT.CalculateVAT(Cart.Total);
|
private long CalculateVAT() => VAT.CalculateVAT(Cart.Total);
|
||||||
|
|
||||||
private static string GetProductImageUrl(string? imageUrl)
|
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)
|
@if (_loading)
|
||||||
{
|
{
|
||||||
<MudContainer MaxWidth="MaxWidth.Medium" Class="py-6">
|
<MudContainer MaxWidth="MaxWidth.Medium" Class="py-6">
|
||||||
<MudStack AlignItems="AlignItems.Center">
|
<LoadingState />
|
||||||
<MudProgressCircular Indeterminate="true" Color="Color.Primary" />
|
|
||||||
</MudStack>
|
|
||||||
</MudContainer>
|
</MudContainer>
|
||||||
}
|
}
|
||||||
else if (_order is null)
|
else if (_order is null)
|
||||||
{
|
{
|
||||||
<MudContainer MaxWidth="MaxWidth.Medium" Class="py-6">
|
<MudContainer MaxWidth="MaxWidth.Medium" Class="py-6">
|
||||||
<MudAlert Severity="Severity.Warning">سفارش یافت نشد.</MudAlert>
|
<EmptyState Icon="@Icons.Material.Filled.SearchOff"
|
||||||
<MudButton Variant="Variant.Text" StartIcon="@Icons.Material.Filled.ArrowBack" OnClick="() => Navigation.NavigateTo(RouteConstants.Store.Orders)">بازگشت</MudButton>
|
Title="سفارش یافت نشد."
|
||||||
|
ActionText="بازگشت"
|
||||||
|
ActionHref="@RouteConstants.Store.Orders" />
|
||||||
</MudContainer>
|
</MudContainer>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
||||||
|
<PageHeader Title="@($"جزئیات سفارش #{_order.Id}")" BackHref="@RouteConstants.Store.Orders" />
|
||||||
<MudPaper Elevation="2" Class="pa-4 rounded-lg">
|
<MudPaper Elevation="2" Class="pa-4 rounded-lg">
|
||||||
<MudStack Spacing="2">
|
<MudStack Spacing="2">
|
||||||
<MudText Typo="Typo.h5">سفارش #@_order.Id</MudText>
|
@if (_order.PaymentDate != null)
|
||||||
<MudText Typo="Typo.body2" Class="mud-text-secondary">تاریخ: @_order.PaymentDate.ToDateTime().MiladiToJalaliWithTime()</MudText>
|
{
|
||||||
|
<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">وضعیت: @GetStatusText(_order.PaymentStatus)</MudText>
|
||||||
<MudText Typo="Typo.body2">روش پرداخت: @GetPaymentMethodText(_order.PaymentMethod)</MudText>
|
|
||||||
<MudText Typo="Typo.body2">آدرس: @_order.UserAddressText</MudText>
|
<MudText Typo="Typo.body2">آدرس: @_order.UserAddressText</MudText>
|
||||||
<MudDivider Class="my-2" />
|
<MudDivider Class="my-2" />
|
||||||
<MudHidden Breakpoint="Breakpoint.MdAndUp" Invert="true">
|
<MudHidden Breakpoint="Breakpoint.MdAndUp" Invert="true">
|
||||||
@@ -40,14 +46,14 @@ else
|
|||||||
<RowTemplate>
|
<RowTemplate>
|
||||||
<MudTd>
|
<MudTd>
|
||||||
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
|
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
|
||||||
<MudImage Src="@GetProductImageUrl(context.ProductThumbnailPath)" Alt="@context.ProductTitle"
|
<AppImage Path="@GetProductImageUrl(context.ProductThumbnailPath)" Alt="@context.ProductTitle"
|
||||||
Width="60" Height="60" Class="rounded-circle" />
|
ImgWidth="60" ImgHeight="60" Class="rounded-lg" ObjectFit="ObjectFit.Cover" />
|
||||||
<MudText>@context.ProductThumbnailPath</MudText>
|
<MudText>@context.ProductTitle</MudText>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
</MudTd>
|
</MudTd>
|
||||||
<MudTd>@FormatPrice(context.UnitPrice.Value)</MudTd>
|
<MudTd>@FormatPrice(context.UnitPrice ?? 0)</MudTd>
|
||||||
<MudTd>@context.Count</MudTd>
|
<MudTd>@context.Count</MudTd>
|
||||||
<MudTd>@FormatPrice(context.UnitPrice.Value*context.Count.Value)</MudTd>
|
<MudTd>@FormatPrice((context.UnitPrice ?? 0) * (context.Count ?? 0))</MudTd>
|
||||||
</RowTemplate>
|
</RowTemplate>
|
||||||
</MudTable>
|
</MudTable>
|
||||||
</MudHidden>
|
</MudHidden>
|
||||||
@@ -59,15 +65,15 @@ else
|
|||||||
<MudPaper Class="pa-3 rounded-lg" Outlined="true">
|
<MudPaper Class="pa-3 rounded-lg" Outlined="true">
|
||||||
<MudStack Spacing="1">
|
<MudStack Spacing="1">
|
||||||
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
|
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
|
||||||
<MudImage Src="@GetProductImageUrl(it.ProductThumbnailPath)" Alt="@it.ProductTitle"
|
<AppImage Path="@GetProductImageUrl(it.ProductThumbnailPath)" Alt="@it.ProductTitle"
|
||||||
Width="60" Height="60" Class="rounded-circle" />
|
ImgWidth="60" ImgHeight="60" Class="rounded-lg" ObjectFit="ObjectFit.Cover" />
|
||||||
<MudText>@it.ProductTitle</MudText>
|
<MudText>@it.ProductTitle</MudText>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
<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>
|
<MudText>تعداد: @it.Count</MudText>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
<MudText>جمع: @FormatPrice(it.UnitPrice.Value*it.Count.Value)</MudText>
|
<MudText>جمع: @FormatPrice((it.UnitPrice ?? 0) * (it.Count ?? 0))</MudText>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
</MudPaper>
|
</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 Spacing="1" Class="pa-2" Style="background-color: var(--mud-palette-background-grey); border-radius: 8px;">
|
||||||
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||||||
@@ -94,18 +100,42 @@ else
|
|||||||
</MudStack>
|
</MudStack>
|
||||||
<MudDivider Class="my-1" />
|
<MudDivider Class="my-1" />
|
||||||
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||||||
<MudText Typo="Typo.subtitle1" Style="font-weight: bold;">مبلغ قابل پرداخت:</MudText>
|
<MudText Typo="Typo.subtitle1" Class="fw-bold">مبلغ قابل پرداخت:</MudText>
|
||||||
<MudText Typo="Typo.subtitle1" Color="Color.Primary" Style="font-weight: bold;">@FormatPrice(_order.VatInfo.TotalAmount)</MudText>
|
<MudText Typo="Typo.subtitle1" Color="Color.Primary" Class="fw-bold">@FormatPrice(_order.VatInfo.TotalAmount)</MudText>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||||||
<MudText Typo="Typo.subtitle1" Style="font-weight: bold;">مبلغ قابل پرداخت:</MudText>
|
<MudText Typo="Typo.subtitle1" Class="fw-bold">مبلغ قابل پرداخت:</MudText>
|
||||||
<MudText Typo="Typo.subtitle1" Color="Color.Primary" Style="font-weight: bold;">@FormatPrice(subtotal)</MudText>
|
<MudText Typo="Typo.subtitle1" Color="Color.Primary" Class="fw-bold">@FormatPrice(subtotal)</MudText>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
}
|
}
|
||||||
</MudStack>
|
</MudStack>
|
||||||
|
|
||||||
|
@* نمایش PV سفارش *@
|
||||||
|
@if (_pvData != null && _pvData.TotalPv > 0)
|
||||||
|
{
|
||||||
|
<MudDivider Class="my-2" />
|
||||||
|
<MudStack Spacing="1" Class="pa-2" Style="background-color: var(--mud-palette-background-grey); border-radius: 8px;">
|
||||||
|
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
|
||||||
|
<MudIcon Icon="@Icons.Material.Filled.TrendingUp" Color="Color.Info" Size="Size.Small" />
|
||||||
|
<MudText Typo="Typo.subtitle2" Color="Color.Info">امتیاز فروش (PV)</MudText>
|
||||||
|
</MudStack>
|
||||||
|
@foreach (var product in _pvData.Products)
|
||||||
|
{
|
||||||
|
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||||||
|
<MudText Typo="Typo.caption" Class="mud-text-secondary">@product.ProductTitle × @product.Quantity</MudText>
|
||||||
|
<MudText Typo="Typo.caption">@string.Format("{0:N0}", product.TotalPv) PV</MudText>
|
||||||
|
</MudStack>
|
||||||
|
}
|
||||||
|
<MudDivider Class="my-1" />
|
||||||
|
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||||||
|
<MudText Typo="Typo.body2" Class="fw-bold">جمع PV:</MudText>
|
||||||
|
<MudText Typo="Typo.body2" Color="Color.Info" Class="fw-bold">@string.Format("{0:N0}", _pvData.TotalPv) PV</MudText>
|
||||||
|
</MudStack>
|
||||||
|
</MudStack>
|
||||||
|
}
|
||||||
</MudStack>
|
</MudStack>
|
||||||
</MudPaper>
|
</MudPaper>
|
||||||
</MudContainer>
|
</MudContainer>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user