Compare commits
170 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f743121eb2 | |||
| 76d96c2544 | |||
| b60d1da4b7 | |||
| 016b259da0 | |||
| bb789e443e | |||
| 0c1842cf02 | |||
| 392e038913 | |||
| d7f25efce2 | |||
| 5246d8c1ba | |||
| f5bfdba124 | |||
| c2c07c6b89 | |||
| 764742dbe7 | |||
| 955736d345 | |||
| ea357e9865 | |||
| a4061c041b | |||
| a108f89213 | |||
| c71062decf | |||
| c815f8d358 | |||
| 27a3ea76c5 | |||
| 0d0ca252a6 | |||
| aedf312812 | |||
| 690aaa714b | |||
| 8252e408b6 | |||
| 186abe042f | |||
| c6089f8dc3 | |||
| 101687f9db | |||
| 307302e33d | |||
| c243b59113 | |||
| 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 | |||
| 98a68074bc |
@@ -0,0 +1,2 @@
|
|||||||
|
# Keep production config from current branch during merges — never ov erwrite
|
||||||
|
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
|
|
||||||
@@ -6,15 +6,15 @@ on:
|
|||||||
- kub-stage
|
- kub-stage
|
||||||
|
|
||||||
env:
|
env:
|
||||||
REGISTRY: 194.5.195.53:30080
|
REGISTRY: 185.252.31.44:30080
|
||||||
IMAGE_NAME: admin/frontoffice
|
IMAGE_NAME: admin/frontoffice
|
||||||
K8S_SERVER: 194.5.195.53
|
K8S_SERVER: 185.252.31.44
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-and-deploy:
|
build-and-deploy:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container:
|
container:
|
||||||
image: 194.5.195.53:32082/docker-sshpass:latest
|
image: 185.252.31.44:32082/docker-sshpass:latest
|
||||||
options: --privileged
|
options: --privileged
|
||||||
steps:
|
steps:
|
||||||
- name: Start Docker daemon
|
- name: Start Docker daemon
|
||||||
@@ -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:32500", "194.5.195.53:32082"]
|
"insecure-registries": ["185.252.31.44:30080", "185.252.31.44:32500", "185.252.31.44:32082"]
|
||||||
}
|
}
|
||||||
DAEMON
|
DAEMON
|
||||||
echo "🚀 Starting Docker daemon..."
|
echo "🚀 Starting Docker daemon..."
|
||||||
dockerd &
|
dockerd --iptables=false --ip6tables=false --bridge=none --storage-driver=vfs &
|
||||||
|
|
||||||
# Wait up to 3 minutes for Docker to be ready
|
# Wait up to 3 minutes for Docker to be ready
|
||||||
for i in $(seq 1 90); do
|
for i in $(seq 1 90); do
|
||||||
@@ -50,21 +50,37 @@ jobs:
|
|||||||
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 .
|
||||||
|
|
||||||
|
- name: Login to Docker registries
|
||||||
|
run: |
|
||||||
|
echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login 185.252.31.44: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 -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest -f FrontOffice.Main/Dockerfile .
|
DOCKER_BUILDKIT=0 docker build --network host -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest -f FrontOffice.Main/Dockerfile .
|
||||||
|
|
||||||
- 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 }}:latest
|
docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||||
|
|
||||||
- name: Deploy to Kubernetes
|
- name: Deploy to Kubernetes
|
||||||
run: |
|
run: |
|
||||||
export SSHPASS="${{ secrets.SERVER_PASSWORD }}"
|
export SSHPASS="${{ secrets.SERVER_PASSWORD }}"
|
||||||
|
|
||||||
|
# Copy K8s manifests to server
|
||||||
|
sshpass -e scp -o StrictHostKeyChecking=no k8s/staging/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 }} "
|
sshpass -e ssh -o StrictHostKeyChecking=no root@${{ env.K8S_SERVER }} "
|
||||||
kubectl rollout restart deployment/frontoffice
|
kubectl apply -n kbs-stage -f /tmp/frontoffice-deployment.yaml &&
|
||||||
kubectl rollout status deployment/frontoffice --timeout=180s
|
# Drop cached :latest so IfNotPresent/Always both pick the new digest
|
||||||
|
# (crictl is broken on this host — use containerd ctr)
|
||||||
|
ctr -n k8s.io images ls -q | grep -E 'frontoffice(:latest|@)' | while read -r ref; do
|
||||||
|
ctr -n k8s.io images rm \"\$ref\" 2>/dev/null || true
|
||||||
|
done
|
||||||
|
kubectl -n kbs-stage rollout restart deployment/frontoffice &&
|
||||||
|
kubectl -n kbs-stage rollout status deployment/frontoffice --timeout=180s &&
|
||||||
|
rm -f /tmp/frontoffice-deployment.yaml
|
||||||
"
|
"
|
||||||
echo "✅ Deployed!"
|
echo "✅ Deployed!"
|
||||||
|
|||||||
@@ -6,84 +6,78 @@ on:
|
|||||||
- production
|
- production
|
||||||
|
|
||||||
env:
|
env:
|
||||||
REGISTRY: gitea-svc:3000
|
REGISTRY: 185.252.31.44:30080
|
||||||
<<<<<<< HEAD
|
|
||||||
EXTERNAL_REGISTRY: 194.5.195.53:30080
|
|
||||||
=======
|
|
||||||
EXTERNAL_REGISTRY: git.kbs1.ir
|
|
||||||
>>>>>>> production
|
|
||||||
IMAGE_NAME: admin/frontoffice
|
IMAGE_NAME: admin/frontoffice
|
||||||
|
K8S_SERVER: 45.149.79.127
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-and-deploy:
|
build-and-deploy:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container:
|
container:
|
||||||
image: docker:latest
|
image: 185.252.31.44:32082/docker-sshpass:latest
|
||||||
options: --privileged
|
options: --privileged
|
||||||
env:
|
|
||||||
HTTP_PROXY: http://proxyuser:87zH26nbqT2@46.249.98.211:3128
|
|
||||||
HTTPS_PROXY: http://proxyuser:87zH26nbqT2@46.249.98.211:3128
|
|
||||||
NO_PROXY: localhost,127.0.0.1,gitea-svc,45.149.79.127,194.5.195.53,10.0.0.0/8
|
|
||||||
steps:
|
steps:
|
||||||
# - name: Install dependencies
|
|
||||||
# run: |
|
|
||||||
# apk add --no-cache git curl
|
|
||||||
# curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
|
|
||||||
# chmod +x kubectl
|
|
||||||
# mv kubectl /usr/local/bin/
|
|
||||||
|
|
||||||
- name: Start Docker daemon
|
- 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'
|
||||||
{
|
{
|
||||||
<<<<<<< HEAD
|
"insecure-registries": ["185.252.31.44:30080", "185.252.31.44:32500", "185.252.31.44:32082"]
|
||||||
"insecure-registries": ["194.5.195.53:30080", "gitea-svc:3000"]
|
|
||||||
=======
|
|
||||||
"insecure-registries": ["194.5.195.53:32082", "git.kbs1.ir", "gitea-svc:3000"]
|
|
||||||
>>>>>>> production
|
|
||||||
}
|
}
|
||||||
DAEMON
|
DAEMON
|
||||||
mkdir -p ~/.docker
|
echo "🚀 Starting Docker daemon..."
|
||||||
cat > ~/.docker/config.json << 'CONF'
|
dockerd --iptables=false --ip6tables=false --bridge=none --storage-driver=vfs &
|
||||||
{
|
|
||||||
"proxies": {
|
for i in $(seq 1 90); do
|
||||||
"default": {
|
if docker info >/dev/null 2>&1; then
|
||||||
"httpProxy": "http://proxyuser:87zH26nbqT2@46.249.98.211:3128",
|
echo "✅ Docker daemon is ready (attempt $i)"
|
||||||
"httpsProxy": "http://proxyuser:87zH26nbqT2@46.249.98.211:3128",
|
docker version
|
||||||
"noProxy": "localhost,127.0.0.1,gitea-svc,194.5.195.53,10.0.0.0/8"
|
break
|
||||||
}
|
else
|
||||||
}
|
echo "⏳ Waiting for Docker daemon... (attempt $i/90)"
|
||||||
}
|
sleep 2
|
||||||
CONF
|
fi
|
||||||
dockerd &
|
done
|
||||||
for i in $(seq 1 30); do docker info >/dev/null 2>&1 && break || sleep 2; done
|
|
||||||
|
if ! docker info >/dev/null 2>&1; then
|
||||||
|
echo "❌ Docker daemon failed to start"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
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 .
|
||||||
git log -1 --format="%H %s"
|
|
||||||
|
- name: Login to Docker registries
|
||||||
|
run: |
|
||||||
|
echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login 185.252.31.44: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 \
|
||||||
-t ${{ env.EXTERNAL_REGISTRY }}/${{ env.IMAGE_NAME }}:prod \
|
|
||||||
.
|
.
|
||||||
|
|
||||||
- name: Push to Registry
|
- name: Push to Registry
|
||||||
run: |
|
run: |
|
||||||
echo "87zH26nbqT" | 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
|
||||||
echo "87zH26nbqT" | docker login ${{ env.EXTERNAL_REGISTRY }} -u admin --password-stdin
|
|
||||||
docker push ${{ env.EXTERNAL_REGISTRY }}/${{ env.IMAGE_NAME }}:prod
|
|
||||||
|
|
||||||
- name: Deploy to Production
|
- name: Deploy to Production
|
||||||
run: |
|
run: |
|
||||||
mkdir -p ~/.kube
|
export SSHPASS="${{ secrets.SERVER_PASSWORD }}"
|
||||||
echo "${{ secrets.KUBECONFIG_PROD }}" | base64 -d > ~/.kube/config
|
|
||||||
kubectl rollout restart deployment/frontoffice || echo "Deployment not found"
|
# Copy K8s manifests to server
|
||||||
kubectl rollout status deployment/frontoffice --timeout=5m || echo "Rollout pending"
|
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,38 +0,0 @@
|
|||||||
# 🎉 بهروزرسانی جدید - نسخه ۱.۵.۰
|
|
||||||
|
|
||||||
**تاریخ انتشار**: ۹ دی ۱۴۰۴
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✨ امکانات جدید
|
|
||||||
|
|
||||||
### 💰 بهبود صفحه پاداشها
|
|
||||||
- **انتخابگر هفته هوشمند**: حالا میتونید با تایپ کردن، هفته مورد نظر رو سریعتر پیدا کنید
|
|
||||||
- **نمایش خلاصه**: در بالای صفحه، مجموع پاداشها، مبلغ پرداخت شده و در انتظار رو ببینید
|
|
||||||
- **طراحی جدید موبایل**: تجربه بهتر در گوشی موبایل
|
|
||||||
|
|
||||||
### 📊 جزئیات بیشتر در گزارش هفتگی
|
|
||||||
- **نمایش اعضای جدید**: تعداد اعضای جدید هر تیم در هفته
|
|
||||||
- **انتقال از هفته قبل**: مشاهده امتیازات منتقل شده از هفته گذشته
|
|
||||||
|
|
||||||
### 🎨 بهبود رابط کاربری
|
|
||||||
- طراحی زیباتر کارتها و جداول
|
|
||||||
- نمایش بهتر در تمام اندازههای صفحه نمایش
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🐛 رفع اشکال
|
|
||||||
|
|
||||||
- رفع مشکل نمایش نادرست امتیازات منتقل شده
|
|
||||||
- بهبود سرعت بارگذاری صفحات
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 💡 نکته
|
|
||||||
|
|
||||||
برای دسترسی به پاداشهای خود، از منوی **پروفایل** گزینه **پاداشهای من** را انتخاب کنید.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
با تشکر از همراهی شما 🙏
|
|
||||||
**تیم کارا بازار سلامت**
|
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# Docs moved to totalDoc
|
||||||
|
|
||||||
|
See [totalDoc/INDEX.md](../../totalDoc/INDEX.md) for all documentation.
|
||||||
|
|
||||||
@@ -0,0 +1,262 @@
|
|||||||
|
# سرویسهای 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.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: 185.252.31.44: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,85 @@
|
|||||||
|
---
|
||||||
|
# FrontOffice (Blazor Server) staging.
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: frontoffice
|
||||||
|
namespace: kbs-stage
|
||||||
|
labels:
|
||||||
|
app: frontoffice
|
||||||
|
environment: staging
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: frontoffice
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: frontoffice
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: frontoffice
|
||||||
|
image: 185.252.31.44:30080/admin/frontoffice:latest
|
||||||
|
# Staging uses :latest — Always avoids stale node cache after registry retag
|
||||||
|
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: kbs-stage
|
||||||
|
labels:
|
||||||
|
app: frontoffice
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: frontoffice
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
targetPort: 80
|
||||||
|
name: http
|
||||||
|
type: ClusterIP
|
||||||
|
|
||||||
|
---
|
||||||
|
# FrontOffice Ingress
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: frontoffice-ingress
|
||||||
|
namespace: kbs-stage
|
||||||
|
annotations:
|
||||||
|
cert-manager.io/cluster-issuer: "letsencrypt-prod"
|
||||||
|
nginx.ingress.kubernetes.io/ssl-redirect: "true"
|
||||||
|
spec:
|
||||||
|
ingressClassName: nginx
|
||||||
|
tls:
|
||||||
|
- hosts:
|
||||||
|
- frontoffice.se.kbs1.ir
|
||||||
|
secretName: frontoffice-tls
|
||||||
|
rules:
|
||||||
|
- host: frontoffice.se.kbs1.ir
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: frontoffice-svc
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM 194.5.195.53:32082/dotnet/sdk:9.0 AS build
|
FROM 185.252.31.44:32082/dotnet/sdk:9.0 AS build
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
|
|
||||||
# Copy NuGet config and project file
|
# Copy NuGet config and project file
|
||||||
@@ -16,10 +16,14 @@ WORKDIR "/src/FrontOffice.Main"
|
|||||||
RUN dotnet publish "FrontOffice.Main.csproj" -c Release -o /app/publish
|
RUN dotnet publish "FrontOffice.Main.csproj" -c Release -o /app/publish
|
||||||
|
|
||||||
# Runtime stage - aspnet for Blazor Server
|
# Runtime stage - aspnet for Blazor Server
|
||||||
FROM 194.5.195.53:32082/dotnet/aspnet:9.0 AS runtime
|
FROM 185.252.31.44: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,7 @@
|
|||||||
|
|
||||||
<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" />
|
|
||||||
<PackageReference Include="Foursat.FrontOffice.BFF.ClubMembership.Protobuf" Version="0.0.4" />
|
|
||||||
<PackageReference Include="Foursat.FrontOffice.BFF.Commission.Protobuf" Version="0.0.6" />
|
|
||||||
<PackageReference Include="Foursat.FrontOffice.BFF.Configuration.Protobuf" Version="0.0.4" />
|
|
||||||
<!-- <PackageReference Include="Foursat.FrontOffice.BFF.ClubMembership.Protobuf" Version="0.0.3" /> -->
|
|
||||||
<!-- <PackageReference Include="Foursat.FrontOffice.BFF.Commission.Protobuf" Version="0.0.2" /> -->
|
|
||||||
<PackageReference Include="Foursat.FrontOffice.BFF.DiscountShop.Protobuf" Version="0.0.3" />
|
|
||||||
<PackageReference Include="Foursat.FrontOffice.BFF.NetworkMembership.Protobuf" Version="0.0.5" />
|
|
||||||
<PackageReference Include="Foursat.FrontOffice.BFF.Package.Protobuf" Version="0.0.114" />
|
|
||||||
<!-- <PackageReference Include="Foursat.FrontOffice.BFF.NetworkMembership.Protobuf" Version="0.0.2" />-->
|
|
||||||
<!-- <PackageReference Include="Foursat.FrontOffice.BFF.Package.Protobuf" Version="0.0.113" /> -->
|
|
||||||
<PackageReference Include="Foursat.FrontOffice.BFF.Products.Protobuf" Version="0.0.18" />
|
|
||||||
<PackageReference Include="Foursat.FrontOffice.BFF.Transaction.Protobuf" Version="0.0.113" />
|
|
||||||
<PackageReference Include="Foursat.FrontOffice.BFF.Category.Protobuf" Version="0.0.14" />
|
|
||||||
<PackageReference Include="Foursat.FrontOffice.BFF.User.Protobuf" Version="0.0.118" />
|
|
||||||
<!-- <PackageReference Include="Foursat.FrontOffice.BFF.User.Protobuf" Version="0.0.117" /> -->
|
|
||||||
<PackageReference Include="Foursat.FrontOffice.BFF.UserAddress.Protobuf" Version="0.0.116" />
|
|
||||||
<!-- <PackageReference Include="Foursat.FrontOffice.BFF.UserOrder.Protobuf" Version="0.0.115" />-->
|
|
||||||
<PackageReference Include="Foursat.FrontOffice.BFF.ShopingCart.Protobuf" Version="0.0.17" />
|
|
||||||
<PackageReference Include="Foursat.FrontOffice.BFF.UserOrder.Protobuf" Version="0.0.116" />
|
|
||||||
<PackageReference Include="Foursat.FrontOffice.BFF.UserWallet.Protobuf" Version="0.0.16" />
|
|
||||||
<!-- UserWallet moved to ProjectReference for latest proto with WeekDefinitionId -->
|
|
||||||
<!-- <PackageReference Include="Foursat.FrontOffice.BFF.UserWallet.Protobuf" Version="0.0.15" /> -->
|
|
||||||
<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" />
|
||||||
@@ -44,6 +22,19 @@
|
|||||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="9.0.0" />
|
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="9.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<!-- ============================================================ -->
|
||||||
|
<!-- Proto References: Direct reference to CMS Protobuf project -->
|
||||||
|
<!-- This enables direct communication with CMS (no BFF needed) -->
|
||||||
|
<!-- ============================================================ -->
|
||||||
|
|
||||||
|
<!-- CMS Protobuf: NuGet in CI/Docker; ProjectReference for local monorepo dev -->
|
||||||
|
<ItemGroup Condition="Exists('../../../CMS/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj')">
|
||||||
|
<ProjectReference Include="../../../CMS/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup Condition="!Exists('../../../CMS/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj')">
|
||||||
|
<PackageReference Include="Foursat.CMSMicroservice.Protobuf" Version="0.0.213" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<!-- Local Proto Project Reference for development -->
|
<!-- Local Proto Project Reference for development -->
|
||||||
<!-- <ItemGroup>-->
|
<!-- <ItemGroup>-->
|
||||||
<!-- <ProjectReference Include="..\..\..\FrontOffice.BFF\src\Protobufs\FrontOffice.BFF.Package.Protobuf\FrontOffice.BFF.Package.Protobuf.csproj" />-->
|
<!-- <ProjectReference Include="..\..\..\FrontOffice.BFF\src\Protobufs\FrontOffice.BFF.Package.Protobuf\FrontOffice.BFF.Package.Protobuf.csproj" />-->
|
||||||
@@ -76,12 +67,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>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<packageSources>
|
<packageSources>
|
||||||
<clear />
|
<clear />
|
||||||
<!-- Nexus (hosts FourSat packages + proxies nuget.org) -->
|
<!-- 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://185.252.31.44:32081/repository/nuget-all/index.json" allowInsecureConnections="true" />
|
||||||
</packageSources>
|
</packageSources>
|
||||||
<packageSourceCredentials>
|
<packageSourceCredentials>
|
||||||
<Nexus>
|
<Nexus>
|
||||||
|
|||||||
@@ -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,47 @@
|
|||||||
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>
|
||||||
|
|
||||||
|
<MudButton Variant="Variant.Filled"
|
||||||
|
Color="Color.Info"
|
||||||
|
Size="Size.Large"
|
||||||
|
FullWidth="true"
|
||||||
|
StartIcon="@Icons.Material.Filled.AccountBalance"
|
||||||
|
OnClick="ProcessTwentyCardPayment"
|
||||||
|
Disabled="@(!CanProceedToPayment || _isProcessingPayment || !_selectedPackage.SupportsDirectPurchase)"
|
||||||
|
Class="mt-2">
|
||||||
|
پرداخت با ۲۰کارت
|
||||||
|
</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;
|
||||||
@@ -171,13 +107,39 @@ public partial class Checkout
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async Task ProcessPayment()
|
private async Task ProcessPayment()
|
||||||
{
|
=> await StartGatewayPayment(gatewayProvider: null);
|
||||||
Snackbar.Add("درگاه پرداخت متصل نیست! لطفا در زمان دیگری مجددا تلاش فرمایید!", Severity.Warning);
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!CanProceedToPayment || _selectedPackage == null || _selectedAddress == null)
|
private async Task ProcessTwentyCardPayment()
|
||||||
|
{
|
||||||
|
if (!CanProceedToPayment || _selectedPackage == null)
|
||||||
{
|
{
|
||||||
Snackbar.Add("لطفاً پکیج و آدرس را انتخاب کنید.", Severity.Warning);
|
Snackbar.Add("لطفاً پکیج را انتخاب کنید.", Severity.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var options = new DialogOptions
|
||||||
|
{
|
||||||
|
MaxWidth = MaxWidth.Small,
|
||||||
|
FullWidth = true,
|
||||||
|
CloseOnEscapeKey = true,
|
||||||
|
BackdropClick = true
|
||||||
|
};
|
||||||
|
|
||||||
|
var dialog = await DialogService.ShowAsync<Shared.TwentyCardCreditDialog>(
|
||||||
|
string.Empty, options);
|
||||||
|
var result = await dialog.Result;
|
||||||
|
if (result.Canceled) return;
|
||||||
|
|
||||||
|
await StartGatewayPayment(gatewayProvider: "20card");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task StartGatewayPayment(string? gatewayProvider)
|
||||||
|
{
|
||||||
|
if (_isProcessingPayment) return;
|
||||||
|
|
||||||
|
if (!CanProceedToPayment || _selectedPackage == null)
|
||||||
|
{
|
||||||
|
Snackbar.Add("لطفاً پکیج را انتخاب کنید.", Severity.Warning);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,33 +147,30 @@ public partial class Checkout
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Step 1: Create payment request
|
var request = 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
|
||||||
};
|
};
|
||||||
|
if (!string.IsNullOrWhiteSpace(gatewayProvider))
|
||||||
|
request.GatewayProvider = gatewayProvider;
|
||||||
|
|
||||||
var orderResponse = await UserOrderContract.CreateNewUserOrderAsync(orderRequest);
|
var response = await PackageClient.CustomerPurchasePackageAsync(request);
|
||||||
|
|
||||||
// Step 3: Redirect to payment gateway
|
if (!response.Success)
|
||||||
Navigation.NavigateTo(paymentResponse.PaymentGWUrl);
|
{
|
||||||
|
Snackbar.Add(response.Message ?? "خطا در آغاز فرآیند پرداخت", Severity.Error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(response.PaymentGatewayUrl))
|
||||||
|
{
|
||||||
|
Navigation.NavigateTo(response.PaymentGatewayUrl, forceLoad: true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Snackbar.Add("خطا در دریافت آدرس درگاه پرداخت", Severity.Error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -224,5 +183,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);
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,11 @@
|
|||||||
<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,159 @@
|
|||||||
|
@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" Color="Color.Primary"
|
||||||
|
StartIcon="@Icons.Material.Filled.Add"
|
||||||
|
OnClick="OpenAddAddressDialog">افزودن آدرس</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>
|
||||||
|
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
|
||||||
|
@if (address.IsDefault)
|
||||||
|
{
|
||||||
|
<MudChip T="string" Color="Color.Success" Variant="Variant.Outlined" Size="Size.Small">پیشفرض</MudChip>
|
||||||
|
}
|
||||||
|
<span @onclick:stopPropagation="true">
|
||||||
|
<MudIconButton Icon="@Icons.Material.Filled.Edit"
|
||||||
|
Size="Size.Small"
|
||||||
|
Color="Color.Primary"
|
||||||
|
aria-label="ویرایش آدرس"
|
||||||
|
OnClick="@(() => OpenEditAddressDialog(address))" />
|
||||||
|
</span>
|
||||||
|
</MudStack>
|
||||||
|
</MudStack>
|
||||||
|
</MudPaper>
|
||||||
|
}
|
||||||
|
<MudButton Class="mt-2" Variant="Variant.Text" Color="Color.Primary"
|
||||||
|
StartIcon="@Icons.Material.Filled.Add"
|
||||||
|
OnClick="OpenAddAddressDialog">افزودن آدرس جدید</MudButton>
|
||||||
|
</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,156 @@
|
|||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using CMSMicroservice.Protobuf.Protos.UserAddress;
|
||||||
|
using FrontOffice.Main.Pages.Profile.Components;
|
||||||
|
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();
|
||||||
|
_selectedAddress = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Snackbar.Add($"خطا در بارگذاری آدرسها: {ex.Message}", Severity.Error);
|
||||||
|
_addresses = new();
|
||||||
|
_selectedAddress = null;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_loadingAddresses = false;
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OpenAddAddressDialog()
|
||||||
|
{
|
||||||
|
var dialog = await DialogService.ShowAsync<AddAddressDialog>("افزودن آدرس جدید");
|
||||||
|
var result = await dialog.Result;
|
||||||
|
if (result is { Canceled: false })
|
||||||
|
{
|
||||||
|
await AuthService.RefreshTokenAsync();
|
||||||
|
await LoadAddresses();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OpenEditAddressDialog(CustomerAddressModel address)
|
||||||
|
{
|
||||||
|
var dialog = await DialogService.ShowAsync<EditAddressDialog>("ویرایش آدرس", new DialogParameters<EditAddressDialog>
|
||||||
|
{
|
||||||
|
{ x => x.Model, address }
|
||||||
|
});
|
||||||
|
var result = await dialog.Result;
|
||||||
|
if (result is { Canceled: false })
|
||||||
|
{
|
||||||
|
await LoadAddresses();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,563 @@
|
|||||||
@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);">
|
1c. TWENTYCARD CREDIT — promo box
|
||||||
<MudImage Src="images/team-meeting.jpg"
|
═══════════════════════════════════════════════ *@
|
||||||
Alt="جلسه تیم فروش"
|
<section class="py-4" style="background:var(--mud-palette-surface);">
|
||||||
ObjectFit="ObjectFit.Cover"
|
<MudContainer MaxWidth="MaxWidth.Medium">
|
||||||
ObjectPosition="ObjectPosition.Center"
|
<MudPaper Elevation="0" Class="landing-twentycard-box rounded-xl pa-4 pa-md-5">
|
||||||
Style="width:100%"
|
<MudStack Row="true" Spacing="3" AlignItems="AlignItems.Start" Class="flex-wrap">
|
||||||
Class="rounded-xl" />
|
<MudAvatar Color="Color.Info" Variant="Variant.Filled" Size="Size.Large" Class="rounded-lg" Style="min-width:56px;">
|
||||||
</MudPaper>
|
<MudIcon Icon="@Icons.Material.Filled.AccountBalance" />
|
||||||
</MudItem>
|
</MudAvatar>
|
||||||
|
<MudStack Spacing="2" Style="flex:1; min-width:220px;">
|
||||||
|
<MudText Typo="Typo.h6">تأمین اعتبار ۲۰کارت</MudText>
|
||||||
|
<MudText Typo="Typo.body2" Class="mud-text-secondary">
|
||||||
|
با تأمین اعتبار از طریق ۲۰کارت میتوانید بدون پرداخت نقدی کامل، هزینه خرید پکیج و محصولات در کارا بازار را پوشش دهید.
|
||||||
|
فرایند درخواست و تأیید اعتبار در پنل ۲۰کارت انجام میشود و پس از آن پرداخت از همان مسیر در دسترس است.
|
||||||
|
این روش برای کسانی مناسب است که میخواهند سریعتر وارد مسیر عضویت و خرید شوند.
|
||||||
|
</MudText>
|
||||||
|
<MudText Typo="Typo.body2">
|
||||||
|
بعد از دریافت وام، میتوانید در کارا بازار از باشگاه مشتریان استفاده کنید و با فروش و دعوت، توسعه سرمایه داشته باشید.
|
||||||
|
</MudText>
|
||||||
|
<div class="mt-1">
|
||||||
|
<MudButton Variant="Variant.Filled"
|
||||||
|
Color="Color.Info"
|
||||||
|
Class="rounded-pill"
|
||||||
|
StartIcon="@Icons.Material.Filled.OpenInNew"
|
||||||
|
Disabled="@string.IsNullOrWhiteSpace(_twentyCardPortalUrl)"
|
||||||
|
OnClick="OpenTwentyCardPortal">
|
||||||
|
ورود به سایت ۲۰کارت
|
||||||
|
</MudButton>
|
||||||
|
</div>
|
||||||
|
</MudStack>
|
||||||
|
</MudStack>
|
||||||
|
</MudPaper>
|
||||||
|
</MudContainer>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
@* ═══════════════════════════════════════════════
|
||||||
|
2. HOW IT WORKS — 3 numbered steps
|
||||||
|
(appsettings Landing:ShowStepsSection; default false)
|
||||||
|
═══════════════════════════════════════════════ *@
|
||||||
|
@if (_showStepsSection)
|
||||||
|
{
|
||||||
|
<section class="section-landing">
|
||||||
|
<MudContainer MaxWidth="MaxWidth.Large">
|
||||||
|
<div class="text-center mb-8 fade-in-up">
|
||||||
|
<MudText Typo="Typo.h3">@(_settings?.StepsTitle ?? "سه گام تا شروع")</MudText>
|
||||||
|
<MudText Typo="Typo.body1" Class="mud-text-secondary mt-2">
|
||||||
|
بدون پیچیدگی، سریع و آسان
|
||||||
|
</MudText>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<MudGrid Spacing="4" Justify="Justify.Center">
|
||||||
|
@foreach (var (step, i) in _steps.Select((s, i) => (s, i)))
|
||||||
|
{
|
||||||
|
<MudItem xs="12" sm="4">
|
||||||
|
<MudPaper Elevation="0" Class="pa-5 rounded-xl feature-card-v2 text-center fade-in-up" data-delay="@(i * 150)">
|
||||||
|
<MudAvatar Size="Size.Large" Color="Color.Primary" Variant="Variant.Filled" Class="mx-auto mb-3 landing-step-num">
|
||||||
|
<MudText Typo="Typo.h5" Style="color:#fff;font-weight:800;">@(i + 1)</MudText>
|
||||||
|
</MudAvatar>
|
||||||
|
<MudText Typo="Typo.h6" Class="mb-1">@step.Title</MudText>
|
||||||
|
<MudText Typo="Typo.body2" Class="mud-text-secondary">@step.Desc</MudText>
|
||||||
|
</MudPaper>
|
||||||
|
</MudItem>
|
||||||
|
}
|
||||||
</MudGrid>
|
</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">
|
(appsettings Landing:ShowFeaturesSection; default false)
|
||||||
|
═══════════════════════════════════════════════ *@
|
||||||
<MudText Typo="Typo.h4" GutterBottom="true">
|
@if (_showFeaturesSection)
|
||||||
چرا کاربران ما را انتخاب میکنند؟
|
{
|
||||||
</MudText>
|
<section class="section-landing" style="background:var(--mud-palette-background-gray);">
|
||||||
<MudText Typo="Typo.subtitle1" Class="mud-text-secondary mb-8">
|
<MudContainer MaxWidth="MaxWidth.Large">
|
||||||
سه دلیل روشن برای شروع امروز.
|
<div class="text-center mb-8 fade-in-up">
|
||||||
|
<MudText Typo="Typo.h3">@(_settings?.FeaturesTitle ?? "چرا کارا بازار سلامت؟")</MudText>
|
||||||
|
<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,295 @@
|
|||||||
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.Extensions.Configuration;
|
||||||
|
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!;
|
||||||
|
[Inject] private IConfiguration Configuration { get; set; } = default!;
|
||||||
|
|
||||||
|
private bool _isAuthenticated;
|
||||||
|
private string _twentyCardPortalUrl = string.Empty;
|
||||||
|
private bool _showStepsSection;
|
||||||
|
private bool _showFeaturesSection;
|
||||||
|
|
||||||
|
// ── 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();
|
||||||
|
_twentyCardPortalUrl = Configuration["TwentyCard:CreditPortalUrl"] ?? string.Empty;
|
||||||
|
// appsettings Landing:* — default false (hidden). Set true to restore sections.
|
||||||
|
_showStepsSection = Configuration.GetValue("Landing:ShowStepsSection", false);
|
||||||
|
_showFeaturesSection = Configuration.GetValue("Landing:ShowFeaturesSection", false);
|
||||||
|
|
||||||
|
// Load landing page settings from CMS
|
||||||
|
try
|
||||||
{
|
{
|
||||||
await InvokeAsync(StateHasChanged);
|
_pageData = await PageSettingsService.GetPageAsync("landing");
|
||||||
};
|
if (_pageData != null)
|
||||||
// await LoadPackagesAsync();
|
{
|
||||||
|
_settings = _pageData.GetSettings<LandingSettings>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Fallback: settings remain null → defaults below
|
||||||
|
}
|
||||||
|
|
||||||
//string mobileNumber = "09387342688";
|
PopulateFromSettings();
|
||||||
|
_dataLoaded = true;
|
||||||
|
|
||||||
//// انکریپت کردن
|
// Load top-selling in-stock products and blog posts in parallel
|
||||||
//string encrypted = Encryptor.EncryptMobileNumber(mobileNumber);
|
var topRegTask = ProductService.GetTopSellingAsync(LandingTopProductCount, inStock: true);
|
||||||
//Console.WriteLine($"Encrypted: {encrypted}");
|
var topDiscTask = DiscountProductService.GetTopSellingAsync(LandingTopProductCount, inStock: true);
|
||||||
|
var featuredPostsTask = BlogPostService.GetFeaturedPostsAsync(2);
|
||||||
|
|
||||||
//// دیکریپت کردن برای تست
|
try
|
||||||
//string decrypted = Encryptor.DecryptMobileNumber(encrypted);
|
{
|
||||||
//Console.WriteLine($"Decrypted: {decrypted}");
|
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 +298,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 +307,75 @@ 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 async Task OpenTwentyCardPortal()
|
||||||
|
|
||||||
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 خودتان مستقر میشود."),
|
if (string.IsNullOrWhiteSpace(_twentyCardPortalUrl)) return;
|
||||||
new("با دیتابیس خودم کار میکند؟", "کاملاً. SQL Server، PostgreSQL و MySQL پشتیبانی میشود."),
|
await JS.InvokeVoidAsync("open", _twentyCardPortalUrl, "_blank");
|
||||||
new("چه درگاههایی پشتیبانی میشود؟", "Stripe و PayPal یا درگاه اختصاصی از طریق وبهوکها."),
|
}
|
||||||
new("میتوانم دادهها را خروجی بگیرم؟", "هر زمان از داشبورد ادمین خروجی CSV/Excel بگیرید."),
|
|
||||||
};
|
private void NavigateToPost(string slug)
|
||||||
|
{
|
||||||
|
Navigation.NavigateTo($"/blog/{slug}");
|
||||||
|
}
|
||||||
|
|
||||||
|
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 bool? StepsEnabled { get; set; }
|
||||||
|
public string? StepsTitle { get; set; }
|
||||||
|
public List<StepItem>? Steps { get; set; }
|
||||||
|
public bool? FeaturesEnabled { 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,132 @@
|
|||||||
|
@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" />
|
||||||
|
|
||||||
|
@* Gateway self-charge temporarily disabled. To restore: remove the alert and set _gatewayChargeEnabled = true in .cs (or flip the if below). *@
|
||||||
|
<MudAlert Severity="MudBlazor.Severity.Info" Class="rounded-lg" Variant="Variant.Outlined">
|
||||||
|
شارژ کیفپول اعتباری از طریق درگاه فعلاً غیرفعال است.
|
||||||
|
<MudButton Size="Size.Small" Variant="Variant.Text" Href="@RouteConstants.Profile.Wallet" Class="mt-1">
|
||||||
|
بازگشت به کیف پول
|
||||||
|
</MudButton>
|
||||||
|
</MudAlert>
|
||||||
|
|
||||||
|
@if (false) @* TEMP: set to true to restore gateway charge UI *@
|
||||||
|
{
|
||||||
|
@* ─── نتیجه پرداخت (اگه از 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">
|
||||||
@@ -52,29 +52,47 @@
|
|||||||
<div id="org-chart-container" class="org-chart-container"></div>
|
<div id="org-chart-container" class="org-chart-container"></div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@* Stats Bar *@
|
@* Stats Bar — فقط آمار خود کاربر (نه نودهای درخت) *@
|
||||||
@if (_statistics != null && !_isLoading)
|
@if (_statistics != null && !_isLoading)
|
||||||
{
|
{
|
||||||
<div class="org-chart-stats">
|
<div class="org-chart-stats">
|
||||||
<div class="stat-item">
|
<div class="org-chart-stats-row">
|
||||||
<span class="stat-label">کل اعضا:</span>
|
<div class="stat-item">
|
||||||
<span class="stat-value">@_statistics.TotalMembers</span>
|
<span class="stat-label">کل اعضا:</span>
|
||||||
|
<span class="stat-value">@_statistics.TotalMembers</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">سازمان چپ:</span>
|
||||||
|
<span class="stat-value left">@_statistics.LeftLegCount</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">سازمان راست:</span>
|
||||||
|
<span class="stat-value right">@_statistics.RightLegCount</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">عمق:</span>
|
||||||
|
<span class="stat-value">@_statistics.TreeDepth</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">پای ضعیف:</span>
|
||||||
|
<span class="stat-value">@(_statistics.WeakerLeg == "Left" ? "چپ" : "راست")</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-item">
|
<div class="org-chart-stats-row leg-scores">
|
||||||
<span class="stat-label">پای چپ:</span>
|
<div class="stat-item leg-score-gold">
|
||||||
<span class="stat-value left">@_statistics.LeftLegCount</span>
|
<span class="stat-label">پکیج طلایی:</span>
|
||||||
|
<span class="stat-value left">چپ @_statistics.GoldLeftLegTotal</span>
|
||||||
|
<span class="stat-sep">·</span>
|
||||||
|
<span class="stat-value right">راست @_statistics.GoldRightLegTotal</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-item">
|
<div class="org-chart-stats-row leg-scores">
|
||||||
<span class="stat-label">پای راست:</span>
|
<div class="stat-item leg-score-silver">
|
||||||
<span class="stat-value right">@_statistics.RightLegCount</span>
|
<span class="stat-label">پکیج نقرهای:</span>
|
||||||
</div>
|
<span class="stat-value left">چپ @_statistics.SilverLeftLegTotal</span>
|
||||||
<div class="stat-item">
|
<span class="stat-sep">·</span>
|
||||||
<span class="stat-label">عمق:</span>
|
<span class="stat-value right">راست @_statistics.SilverRightLegTotal</span>
|
||||||
<span class="stat-value">@_statistics.TreeDepth</span>
|
</div>
|
||||||
</div>
|
|
||||||
<div class="stat-item">
|
|
||||||
<span class="stat-label">پای ضعیف:</span>
|
|
||||||
<span class="stat-value">@(_statistics.WeakerLeg == "Left" ? "چپ" : "راست")</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using FrontOffice.Main.Utilities;
|
using DateTimeConverterCL;
|
||||||
|
using FrontOffice.Main.Utilities;
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
using Microsoft.JSInterop;
|
using Microsoft.JSInterop;
|
||||||
|
|
||||||
@@ -95,7 +96,25 @@ 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,
|
||||||
|
registeredAt = n.JoinedAt.HasValue
|
||||||
|
? n.JoinedAt.Value.ToLocalTime().MiladiToJalali()
|
||||||
|
: "",
|
||||||
|
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,79 @@
|
|||||||
|
@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.Invitees" 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.GroupAdd" Color="Color.Info" />
|
||||||
|
<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,208 @@
|
|||||||
|
|
||||||
<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>
|
<MudButton Variant="Variant.Text"
|
||||||
</MudCard>
|
Color="Color.Primary"
|
||||||
</MudLink>
|
Class="mt-3"
|
||||||
</MudItem>
|
Href="@RouteConstants.Profile.Invitees"
|
||||||
<MudItem xs="6" sm="6" md="3">
|
StartIcon="@Icons.Material.Filled.GroupAdd">
|
||||||
<MudLink Href="@RouteConstants.Profile.Settings" Underline="Underline.None" Class="tile-link">
|
مشاهده دعوتشدگان من
|
||||||
<MudCard Elevation="1" Class="rounded-lg profile-tile">
|
</MudButton>
|
||||||
<MudCardContent Class="d-flex flex-column align-center pa-4">
|
</MudPaper>
|
||||||
<MudIcon Icon="@Icons.Material.Filled.Settings" Size="Size.Large" Color="Color.Primary" />
|
}
|
||||||
<MudText Typo="Typo.subtitle1" Class="mt-2">تنظیمات حساب</MudText>
|
else
|
||||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">اعلانها و تنظیمات</MudText>
|
{
|
||||||
</MudCardContent>
|
<MudPaper Elevation="0" Class="pa-6 rounded-xl text-center" Style="border:2px dashed var(--mud-palette-primary); background:rgba(99,102,241,.04);">
|
||||||
</MudCard>
|
<MudIcon Icon="@Icons.Material.Filled.Lock" Size="Size.Large" Color="Color.Primary" Class="mb-2" Style="font-size:2.5rem;" />
|
||||||
</MudLink>
|
<MudText Typo="Typo.h6" Color="Color.Primary" Class="mb-2">
|
||||||
</MudItem>
|
لینک دعوت شما هنوز فعال نشده است
|
||||||
<MudItem xs="6" sm="6" md="3">
|
</MudText>
|
||||||
<MudLink Href="@RouteConstants.Store.Products" Underline="Underline.None" Class="tile-link">
|
<MudText Typo="Typo.body2" Class="mud-text-secondary mb-1" Style="max-width:560px; margin:0 auto;">
|
||||||
<MudCard Elevation="1" Class="rounded-lg profile-tile">
|
برای فعالسازی لینک دعوت، ابتدا یکی از <strong>پکیجها</strong> را تهیه کنید و سپس در <strong>باشگاه مشتریان</strong> عضو شوید.
|
||||||
<MudCardContent Class="d-flex flex-column align-center pa-4">
|
</MudText>
|
||||||
<MudIcon Icon="@Icons.Material.Filled.Storefront" Size="Size.Large" Color="Color.Primary" />
|
<MudButton Variant="Variant.Outlined"
|
||||||
<MudText Typo="Typo.subtitle1" Class="mt-2">فروشگاه</MudText>
|
Color="Color.Primary"
|
||||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">مشاهده و خرید محصولات</MudText>
|
Class="mt-3"
|
||||||
</MudCardContent>
|
Href="@RouteConstants.Profile.Invitees"
|
||||||
</MudCard>
|
StartIcon="@Icons.Material.Filled.GroupAdd">
|
||||||
</MudLink>
|
دعوتشدگان من
|
||||||
</MudItem>
|
</MudButton>
|
||||||
<MudItem xs="6" sm="6" md="3">
|
<MudText Typo="Typo.body2" Class="mud-text-secondary mb-4" Style="max-width:560px; margin:0 auto;">
|
||||||
<MudLink Href="@RouteConstants.Profile.Wallet" Underline="Underline.None" Class="tile-link">
|
از مزایای ویژه عضویت بهرهمند شده و با فعالیت در زمینه توسعه فروشگاهها پاداش دریافت کنید.
|
||||||
<MudCard Elevation="1" Class="rounded-lg profile-tile">
|
</MudText>
|
||||||
<MudCardContent Class="d-flex flex-column align-center pa-4">
|
<MudButton Variant="Variant.Filled"
|
||||||
<MudIcon Icon="@Icons.Material.Filled.AccountBalanceWallet" Size="Size.Large" Color="Color.Primary" />
|
Color="Color.Primary"
|
||||||
<MudText Typo="Typo.subtitle1" Class="mt-2">کیف پول</MudText>
|
Size="Size.Large"
|
||||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">مدیریت و تاریخچه</MudText>
|
Class="rounded-pill"
|
||||||
</MudCardContent>
|
StartIcon="@Icons.Material.Filled.ShoppingCart"
|
||||||
</MudCard>
|
OnClick="OpenPurchaseOptions">
|
||||||
</MudLink>
|
تهیه پکیج
|
||||||
</MudItem>
|
</MudButton>
|
||||||
<MudItem xs="6" sm="6" md="3">
|
</MudPaper>
|
||||||
<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,33 +395,80 @@ 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, gatewayProvider: null);
|
||||||
|
break;
|
||||||
|
case Shared.PackagePurchaseDialog.PaymentMethodType.TwentyCard:
|
||||||
|
await ProcessTwentyCardPayment(purchase.Package.Id);
|
||||||
|
break;
|
||||||
|
case Shared.PackagePurchaseDialog.PaymentMethodType.DayaLoan:
|
||||||
|
await ProcessDayaLoanPayment();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ClosePurchaseOptions()
|
private async Task ProcessTwentyCardPayment(long packageId)
|
||||||
{
|
{
|
||||||
_showPurchaseBottomSheet = false;
|
var options = new DialogOptions
|
||||||
|
{
|
||||||
|
MaxWidth = MaxWidth.Small,
|
||||||
|
FullWidth = true,
|
||||||
|
CloseOnEscapeKey = true,
|
||||||
|
BackdropClick = true
|
||||||
|
};
|
||||||
|
|
||||||
|
var dialog = await DialogService.ShowAsync<Shared.TwentyCardCreditDialog>(
|
||||||
|
string.Empty, options);
|
||||||
|
var result = await dialog.Result;
|
||||||
|
if (result.Canceled) return;
|
||||||
|
|
||||||
|
await ProcessDirectPayment(packageId, gatewayProvider: "20card");
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task DirectPayment()
|
private async Task ProcessDirectPayment(long packageId, string? gatewayProvider = null)
|
||||||
{
|
{
|
||||||
if (_isProcessingPayment) return;
|
if (_isProcessingPayment) return;
|
||||||
|
|
||||||
_isProcessingPayment = true;
|
_isProcessingPayment = true;
|
||||||
_showPurchaseBottomSheet = false;
|
StateHasChanged();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Create callback URL for payment verification
|
var request = 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 (!string.IsNullOrWhiteSpace(gatewayProvider))
|
||||||
|
request.GatewayProvider = gatewayProvider;
|
||||||
|
|
||||||
|
var response = await PackageContract.CustomerPurchasePackageAsync(request);
|
||||||
|
|
||||||
if (!response.Success)
|
if (!response.Success)
|
||||||
{
|
{
|
||||||
@@ -374,7 +476,6 @@ public partial class Index
|
|||||||
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 +496,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;
|
|
||||||
}
|
|
||||||
if (!_addresses.Any())
|
|
||||||
{
|
|
||||||
_showPurchaseBottomSheet = false;
|
|
||||||
Snackbar.Add($"آدرس محل سکونت شما الزامی است!", Severity.Error);
|
|
||||||
return;
|
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,157 @@
|
|||||||
|
@attribute [Authorize]
|
||||||
|
@attribute [Route(RouteConstants.Profile.Invitees)]
|
||||||
|
@using CMSMicroservice.Protobuf.Protos.User
|
||||||
|
@using CMSMicroservice.Protobuf.Protos.City
|
||||||
|
@inject UserContract.UserContractClient UserClient
|
||||||
|
@inject ISnackbar Snackbar
|
||||||
|
|
||||||
|
<PageTitle>دعوتشدگان من | کارا بازار سلامت</PageTitle>
|
||||||
|
|
||||||
|
<MudContainer MaxWidth="MaxWidth.Medium" Class="py-6">
|
||||||
|
<PageHeader Title="دعوتشدگان من" BackHref="@RouteConstants.Profile.Index" />
|
||||||
|
|
||||||
|
<MudText Typo="Typo.body2" Class="mud-text-secondary mb-4">
|
||||||
|
افرادی که با شماره موبایل شما بهعنوان دعوتکننده ثبتنام کردهاند (متفاوت از سازمان فروش).
|
||||||
|
</MudText>
|
||||||
|
|
||||||
|
@if (_loading)
|
||||||
|
{
|
||||||
|
<MudStack AlignItems="AlignItems.Center" Class="py-8">
|
||||||
|
<MudProgressCircular Indeterminate="true" Color="Color.Primary" />
|
||||||
|
</MudStack>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudGrid Spacing="3" Class="mb-4">
|
||||||
|
<MudItem xs="12" sm="4">
|
||||||
|
<MudPaper Class="pa-4 rounded-xl" Elevation="1">
|
||||||
|
<MudText Typo="Typo.caption" Class="mud-text-secondary">کل دعوتها</MudText>
|
||||||
|
<MudText Typo="Typo.h5">@_stats.TotalInvitees</MudText>
|
||||||
|
</MudPaper>
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="12" sm="4">
|
||||||
|
<MudPaper Class="pa-4 rounded-xl" Elevation="1">
|
||||||
|
<MudText Typo="Typo.caption" Class="mud-text-secondary">فعال در باشگاه</MudText>
|
||||||
|
<MudText Typo="Typo.h5" Color="Color.Success">@_stats.ClubActiveCount</MudText>
|
||||||
|
</MudPaper>
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="12" sm="4">
|
||||||
|
<MudPaper Class="pa-4 rounded-xl" Elevation="1">
|
||||||
|
<MudText Typo="Typo.caption" Class="mud-text-secondary">غیرفعال باشگاه</MudText>
|
||||||
|
<MudText Typo="Typo.h5" Color="Color.Warning">@_stats.ClubInactiveCount</MudText>
|
||||||
|
</MudPaper>
|
||||||
|
</MudItem>
|
||||||
|
</MudGrid>
|
||||||
|
|
||||||
|
<MudPaper Class="pa-3 rounded-xl mb-3" Elevation="0">
|
||||||
|
<MudSelect T="string" Label="فیلتر وضعیت باشگاه" Value="_clubFilter" ValueChanged="OnFilterChanged"
|
||||||
|
Variant="Variant.Outlined" Dense="true" Margin="Margin.Dense">
|
||||||
|
<MudSelectItem Value="@("ALL")">همه</MudSelectItem>
|
||||||
|
<MudSelectItem Value="@("ACTIVE")">فعال باشگاه</MudSelectItem>
|
||||||
|
<MudSelectItem Value="@("INACTIVE")">غیرفعال باشگاه</MudSelectItem>
|
||||||
|
</MudSelect>
|
||||||
|
</MudPaper>
|
||||||
|
|
||||||
|
@if (!_invitees.Any())
|
||||||
|
{
|
||||||
|
<MudAlert Severity="Severity.Info" Dense="true">هنوز کسی با کد دعوت شما ثبتنام نکرده است.</MudAlert>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudTable Items="_invitees" Hover="true" Dense="true" Breakpoint="Breakpoint.Sm" Class="rounded-xl">
|
||||||
|
<HeaderContent>
|
||||||
|
<MudTh>نام</MudTh>
|
||||||
|
<MudTh>تاریخ ثبتنام</MudTh>
|
||||||
|
<MudTh>باشگاه</MudTh>
|
||||||
|
</HeaderContent>
|
||||||
|
<RowTemplate>
|
||||||
|
<MudTd DataLabel="نام">
|
||||||
|
@(string.IsNullOrWhiteSpace($"{context.FirstName} {context.LastName}".Trim())
|
||||||
|
? "—"
|
||||||
|
: $"{context.FirstName} {context.LastName}".Trim())
|
||||||
|
</MudTd>
|
||||||
|
<MudTd DataLabel="تاریخ ثبتنام">
|
||||||
|
@FormatDate(context.RegisteredAt)
|
||||||
|
</MudTd>
|
||||||
|
<MudTd DataLabel="باشگاه">
|
||||||
|
@if (context.IsClubActive)
|
||||||
|
{
|
||||||
|
<MudChip T="string" Size="Size.Small" Color="Color.Success" Variant="Variant.Filled">فعال</MudChip>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudChip T="string" Size="Size.Small" Color="Color.Default" Variant="Variant.Outlined">غیرفعال</MudChip>
|
||||||
|
}
|
||||||
|
</MudTd>
|
||||||
|
</RowTemplate>
|
||||||
|
</MudTable>
|
||||||
|
|
||||||
|
@if (_totalPages > 1)
|
||||||
|
{
|
||||||
|
<MudStack AlignItems="AlignItems.Center" Class="mt-4">
|
||||||
|
<MudPagination Count="_totalPages" Selected="_page" SelectedChanged="OnPageChanged" Color="Color.Primary" />
|
||||||
|
</MudStack>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</MudContainer>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private bool _loading = true;
|
||||||
|
private string _clubFilter = "ALL";
|
||||||
|
private int _page = 1;
|
||||||
|
private const int PageSize = 10;
|
||||||
|
private int _totalPages;
|
||||||
|
private InviteeStats _stats = new();
|
||||||
|
private List<InviteeModel> _invitees = new();
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
await LoadAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnFilterChanged(string value)
|
||||||
|
{
|
||||||
|
_clubFilter = value;
|
||||||
|
_page = 1;
|
||||||
|
await LoadAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnPageChanged(int page)
|
||||||
|
{
|
||||||
|
_page = page;
|
||||||
|
await LoadAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoadAsync()
|
||||||
|
{
|
||||||
|
_loading = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var response = await UserClient.GetMyInviteesAsync(new GetMyInviteesRequest
|
||||||
|
{
|
||||||
|
PaginationState = new PaginationState { PageNumber = _page, PageSize = PageSize },
|
||||||
|
ClubStatusFilter = _clubFilter
|
||||||
|
});
|
||||||
|
|
||||||
|
_stats = response.Stats ?? new InviteeStats();
|
||||||
|
_invitees = response.Invitees.ToList();
|
||||||
|
_totalPages = (int)(response.MetaData?.TotalPage ?? 0);
|
||||||
|
if (_totalPages < 1 && _invitees.Count > 0) _totalPages = 1;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Snackbar.Add($"خطا در بارگذاری دعوتشدگان: {ex.Message}", Severity.Error);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string FormatDate(Google.Protobuf.WellKnownTypes.Timestamp? ts)
|
||||||
|
{
|
||||||
|
if (ts == null) return "—";
|
||||||
|
return ts.ToDateTime().ToLocalTime().MiladiToJalali();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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; }
|
||||||
|
|
||||||
@@ -80,18 +86,49 @@
|
|||||||
[SupplyParameterFromQuery(Name = "Status")]
|
[SupplyParameterFromQuery(Name = "Status")]
|
||||||
private string? Status { get; set; }
|
private string? Status { get; set; }
|
||||||
|
|
||||||
|
/// <summary>۲۰کارت callback — هممعنی Status زرینپال</summary>
|
||||||
|
[SupplyParameterFromQuery(Name = "PaymentStatus")]
|
||||||
|
private string? PaymentStatus { get; set; }
|
||||||
|
|
||||||
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 +136,30 @@
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (OrderId <= 0 || TransactionId <= 0 || string.IsNullOrEmpty(Authority))
|
// ۲۰کارت: PaymentStatus؛ زرینپال: Status
|
||||||
{
|
if (string.IsNullOrWhiteSpace(Status) && !string.IsNullOrWhiteSpace(PaymentStatus))
|
||||||
_isSuccess = false;
|
Status = PaymentStatus;
|
||||||
_message = "پارامترهای پرداخت نامعتبر است";
|
|
||||||
_isLoading = false;
|
|
||||||
StateHasChanged();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var response = await PackageContractClient.VerifyBasePackagePaymentAsync(new VerifyBasePackagePaymentRequest
|
// تعیین نوع پرداخت — اگر type نباشد، پیشفرض خرید پکیج
|
||||||
{
|
var type = PaymentType?.ToLowerInvariant() ?? "package";
|
||||||
OrderId = OrderId,
|
|
||||||
TransactionId = TransactionId,
|
|
||||||
Authority = Authority,
|
|
||||||
Status = Status ?? "NOK"
|
|
||||||
});
|
|
||||||
|
|
||||||
_isSuccess = response.Success;
|
switch (type)
|
||||||
_message = response.Message;
|
|
||||||
_refId = response.RefId;
|
|
||||||
_walletBalance = response.WalletBalance;
|
|
||||||
_discountBalance = response.DiscountBalance;
|
|
||||||
|
|
||||||
// اگر پرداخت موفق بود، توکن را refresh میکنیم
|
|
||||||
// چون 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)
|
||||||
@@ -141,9 +174,185 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <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 +362,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,106 @@
|
|||||||
درخواستهای برداشت
|
درخواستهای برداشت
|
||||||
</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>
|
||||||
|
}
|
||||||
|
|
||||||
|
@* ── Temporarily hidden: discount-wallet gateway charge (restore when needed)
|
||||||
|
<!-- دکمه شارژ کیفپول اعتباری -->
|
||||||
|
<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 +168,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 +215,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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -317,17 +319,17 @@ public partial class RegisterWizard
|
|||||||
$"<p><span class='field-label'>شماره ملی:</span> {_model.NationalCode?.PersianToEnglish()}</p>" +
|
$"<p><span class='field-label'>شماره ملی:</span> {_model.NationalCode?.PersianToEnglish()}</p>" +
|
||||||
$"<p><span class='field-label'>شماره تماس:</span> {_userAuthInfo.MobileNumber?.PersianToEnglish()}</p></div>" +
|
$"<p><span class='field-label'>شماره تماس:</span> {_userAuthInfo.MobileNumber?.PersianToEnglish()}</p></div>" +
|
||||||
"<p>کارابازار و کاربر که در این قرارداد جداگانه «طرف» و مجموعاً «طرفین» نامیده میشوند، با علم و آگاهی کامل، مفاد زیر را پذیرفته و امضا مینمایند.</p></div><div><p class='section-title'>ماده ۱ - موضوع قرارداد</p>" +
|
"<p>کارابازار و کاربر که در این قرارداد جداگانه «طرف» و مجموعاً «طرفین» نامیده میشوند، با علم و آگاهی کامل، مفاد زیر را پذیرفته و امضا مینمایند.</p></div><div><p class='section-title'>ماده ۱ - موضوع قرارداد</p>" +
|
||||||
"<p>موضوع این قرارداد، تعیین حدود و چارچوب تعهدات و سلب مسئولیت کارابازار در قبال استفاده کاربر ازخدمات، باشگاه مشتریان، مزایا، طرحهای همکاری، شبکه معرفی، خرید از فروشگاههای طرفقراردادو هرگونه امکانات و تسهیلات مرتبط است که حسب مورد توسط کارابازار یا از طریق پلتفرمها و شرکتهایهمکار در اختیار کاربر قرار میگیرد، بدون اینکه جزئیات مدل کسبوکار، شیوه تأمین منابع مالی،شبکه همکاری یا ساختار داخلی آن در این قرارداد ذکر یا افشا شود.</p>" +
|
"<p>موضوع این قرارداد، تعیین حدود و چارچوب تعهدات و سلب مسئولیت کارابازار در قبال استفاده کاربر ازخدمات، باشگاه مشتریان، مزایا، طرحهای همکاری، سازمان معرفی، خرید از فروشگاههای طرفقراردادو هرگونه امکانات و تسهیلات مرتبط است که حسب مورد توسط کارابازار یا از طریق پلتفرمها و شرکتهایهمکار در اختیار کاربر قرار میگیرد، بدون اینکه جزئیات مدل کسبوکار، شیوه تأمین منابع مالی،سازمان همکاری یا ساختار داخلی آن در این قرارداد ذکر یا افشا شود.</p>" +
|
||||||
"<p class='section-title'>ماده ۲ - اقرار و آگاهی کاربر</p><p>۲-۱. کاربر اقرار مینماید که کلیه اطلاعات کلی لازم در خصوص نحوه فعالیت، مزایا، ریسکهای احتمالی," +
|
"<p class='section-title'>ماده ۲ - اقرار و آگاهی کاربر</p><p>۲-۱. کاربر اقرار مینماید که کلیه اطلاعات کلی لازم در خصوص نحوه فعالیت، مزایا، ریسکهای احتمالی," +
|
||||||
"سازوکار کلی استفاده از خدمات، و چارچوب کلی همکاری را از مراجع رسمی معرفیشده توسط کارابازاردریافت کرده، سؤالات خود را مطرح نموده و پاسخ مناسب را دریافت نموده است.</p><p>۲-۲. کاربر با امضای این قرارداد اقرار مینماید که:</p><ul><li>با اراده آزاد و بدون هرگونه اجبار، اکراه، فریب یا سوءاستفاده از ناآگاهی وارد این همکاری شده است.</li><li>از احتمال وجود ریسکهای مالی، اعتباری، قانونی و شبکهای مرتبط با فعالیت خود آگاه بوده و آن را میپذیرد.</li>" +
|
"سازوکار کلی استفاده از خدمات، و چارچوب کلی همکاری را از مراجع رسمی معرفیشده توسط کارابازاردریافت کرده، سؤالات خود را مطرح نموده و پاسخ مناسب را دریافت نموده است.</p><p>۲-۲. کاربر با امضای این قرارداد اقرار مینماید که:</p><ul><li>با اراده آزاد و بدون هرگونه اجبار، اکراه، فریب یا سوءاستفاده از ناآگاهی وارد این همکاری شده است.</li><li>از احتمال وجود ریسکهای مالی، اعتباری، قانونی و شبکهای مرتبط با فعالیت خود آگاه بوده و آن را میپذیرد.</li>" +
|
||||||
"<li>هرگونه تصمیم برای استفاده از تسهیلات، مزایا، خدمات باشگاه مشتریان، فعالیت در شبکه معرفی، خرید از فروشگاهها و جذب افراد جدید، تصمیمی شخصی و آگاهانه است و بهعنوان تعهد یا ضمانت بازده مشخص از سوی کارابازار تلقی نمیشود.</li></ul>" +
|
"<li>هرگونه تصمیم برای استفاده از تسهیلات، مزایا، خدمات باشگاه مشتریان، فعالیت در سازمان معرفی، خرید از فروشگاهها و جذب افراد جدید، تصمیمی شخصی و آگاهانه است و بهعنوان تعهد یا ضمانت بازده مشخص از سوی کارابازار تلقی نمیشود.</li></ul>" +
|
||||||
"<p class='section-title'>ماده ۳ - سلب مسئولیت کارابازار</p><p>۳-۱. کاربر صراحتاً میپذیرد که کارابازار صرفاً در چارچوب قوانین و مقررات حاکم و مطابق قراردادها و تفاهمنامههای خود با شرکای تجاری و پلتفرمهای همکار، خدمات و امکانات مشخصی را در اختیار کاربر قرار میدهد و هیچگونه تعهدی نسبت به:</p><ul><li>تحقق سود یا بازده مالی مشخص،</li><li>ثبات ارزش هرگونه اعتبار، امتیاز، یا منافع غیرنقدی که ممکن است در قالب طرحها به کاربر تخصیص یابد،</li>" +
|
"<p class='section-title'>ماده ۳ - سلب مسئولیت کارابازار</p><p>۳-۱. کاربر صراحتاً میپذیرد که کارابازار صرفاً در چارچوب قوانین و مقررات حاکم و مطابق قراردادها و تفاهمنامههای خود با شرکای تجاری و پلتفرمهای همکار، خدمات و امکانات مشخصی را در اختیار کاربر قرار میدهد و هیچگونه تعهدی نسبت به:</p><ul><li>تحقق سود یا بازده مالی مشخص،</li><li>ثبات ارزش هرگونه اعتبار، امتیاز، یا منافع غیرنقدی که ممکن است در قالب طرحها به کاربر تخصیص یابد،</li>" +
|
||||||
"<li>عملکرد اشخاص ثالث (از جمله پلتفرمها، فروشگاهها، شرکتهای بیمه، ارائهدهندگان خدمات سلامت و هر شخص حقیقی یا حقوقی همکار)،</li><li>تصمیمات و اقدامات کاربر در جذب افراد جدید و ایجاد زیرمجموعه،</li></ul><p>نداشته و نخواهد داشت مگر در حدودی که بهصراحت در قرارداد یا قوانین آمره بر عهده کارابازار گذاشته شده است.</p><p>" +
|
"<li>عملکرد اشخاص ثالث (از جمله پلتفرمها، فروشگاهها، شرکتهای بیمه، ارائهدهندگان خدمات سلامت و هر شخص حقیقی یا حقوقی همکار)،</li><li>تصمیمات و اقدامات کاربر در جذب افراد جدید و ایجاد زیرمجموعه،</li></ul><p>نداشته و نخواهد داشت مگر در حدودی که بهصراحت در قرارداد یا قوانین آمره بر عهده کارابازار گذاشته شده است.</p><p>" +
|
||||||
"۳-۲. هرگونه خسارت، دعوی، تهدید، مطالبه، ادعا، شکایت یا پیگیری حقوقی که ناشی از تصمیمات شخصی کاربر،نحوه استفاده وی از تسهیلات و مزایا، جذب دیگران، خرید کالا و خدمات، یا تعاملات او با اشخاص ثالث باشد،مستقیماً متوجه خود کاربر است و کارابازار نسبت به آن هیچگونه مسئولیت مدنی، کیفری، مالی یا قراردادی ندارد," +
|
"۳-۲. هرگونه خسارت، دعوی، تهدید، مطالبه، ادعا، شکایت یا پیگیری حقوقی که ناشی از تصمیمات شخصی کاربر،نحوه استفاده وی از تسهیلات و مزایا، جذب دیگران، خرید کالا و خدمات، یا تعاملات او با اشخاص ثالث باشد،مستقیماً متوجه خود کاربر است و کارابازار نسبت به آن هیچگونه مسئولیت مدنی، کیفری، مالی یا قراردادی ندارد," +
|
||||||
"مگر در مواردی که طبق قوانین آمره، مسئولیت صریح و غیرقابلاسقاط بر عهده کارابازار قرار گرفته باشد.</p><p>۳-۳. کاربر میپذیرد که هیچگونه ادعایی مبنی بر فریب، اغفال، ارائه اطلاعات نادرست یا وعدههای خارج ازچهارچوب رسمی از سوی کارابازار یا نمایندگان رسمی آن، پس از امضای این قرارداد، از وی مسموع نخواهد بود،مگر آنکه طبق رأی قطعی مرجع قضایی صالح، خلاف آن ثابت شود.</p><p class='section-title'>ماده ۴ - مسئولیت کاربر در قبال تهدید، ادعا و شکایت</p>" +
|
"مگر در مواردی که طبق قوانین آمره، مسئولیت صریح و غیرقابلاسقاط بر عهده کارابازار قرار گرفته باشد.</p><p>۳-۳. کاربر میپذیرد که هیچگونه ادعایی مبنی بر فریب، اغفال، ارائه اطلاعات نادرست یا وعدههای خارج ازچهارچوب رسمی از سوی کارابازار یا نمایندگان رسمی آن، پس از امضای این قرارداد، از وی مسموع نخواهد بود،مگر آنکه طبق رأی قطعی مرجع قضایی صالح، خلاف آن ثابت شود.</p><p class='section-title'>ماده ۴ - مسئولیت کاربر در قبال تهدید، ادعا و شکایت</p>" +
|
||||||
"<p>۴-۱. کاربر متعهد است در صورت طرح هرگونه تهدید، ادعا، مطالبه، شکایت یا پیگیری حقوقی از سوی اشخاص ثالث(از جمله افراد جذبشده توسط کاربر، زیرمجموعهها، خریداران معرفیشده یا سایر اشخاص)، که ناشی از عملکردشخصی کاربر، معرفیهای وی، شیوه تبلیغ، وعدههای خارج از چارچوب رسمی کارابازار، یا نقض قوانین و مقرراتباشد، خود رأساً پاسخگو باشد و کارابازار را از هرگونه مسئولیت و هزینه احتمالی در این خصوص مصون بدارد.</p><p>۴-۲. کاربر میپذیرد که:</p><ul><li>هرگونه تهدید یا ادعای بیجا، غیرمنطقی، غیرمستند یا مغایر با مفاد این قرارداد، متوجه شخص کاربر است.</li>" +
|
"<p>۴-۱. کاربر متعهد است در صورت طرح هرگونه تهدید، ادعا، مطالبه، شکایت یا پیگیری حقوقی از سوی اشخاص ثالث(از جمله افراد جذبشده توسط کاربر، زیرمجموعهها، خریداران معرفیشده یا سایر اشخاص)، که ناشی از عملکردشخصی کاربر، معرفیهای وی، شیوه تبلیغ، وعدههای خارج از چارچوب رسمی کارابازار، یا نقض قوانین و مقرراتباشد، خود رأساً پاسخگو باشد و کارابازار را از هرگونه مسئولیت و هزینه احتمالی در این خصوص مصون بدارد.</p><p>۴-۲. کاربر میپذیرد که:</p><ul><li>هرگونه تهدید یا ادعای بیجا، غیرمنطقی، غیرمستند یا مغایر با مفاد این قرارداد، متوجه شخص کاربر است.</li>" +
|
||||||
"<li>در صورت وارد آمدن خسارت مادی یا معنوی به کارابازار ناشی از چنین تهدیدات یا ادعاهایی، کارابازار حق دارد نسبت به مطالبه خسارت از کاربر، طبق قوانین و از طریق مراجع صالح اقدام نماید.</li></ul>" +
|
"<li>در صورت وارد آمدن خسارت مادی یا معنوی به کارابازار ناشی از چنین تهدیدات یا ادعاهایی، کارابازار حق دارد نسبت به مطالبه خسارت از کاربر، طبق قوانین و از طریق مراجع صالح اقدام نماید.</li></ul>" +
|
||||||
"<p class='section-title'>ماده ۵ - محرمانگی و عدم افشای جزئیات کسبوکار</p><p>۵-۱. کاربر میپذیرد که جزئیات فنی، مالی، ساختار شبکه، شیوه توزیع مزایا، قراردادهای داخلی کارابازار باشرکای تجاری و پلتفرمهای همکار، اطلاعات محرمانه تجاری محسوب شده و خارج از این قرارداد است و افشای آنهاتوسط کاربر بدون مجوز کتبی کارابازار، مجاز نمیباشد.</p><p>۵-۲. کاربر حق استناد به عدم درج جزئیات ساختار کسبوکار در این قرارداد را بهعنوان دلیل بطلان، فسخ یاادعای فریب علیه کارابازار از خود سلب مینماید.</p>" +
|
"<p class='section-title'>ماده ۵ - محرمانگی و عدم افشای جزئیات کسبوکار</p><p>۵-۱. کاربر میپذیرد که جزئیات فنی، مالی، ساختار سازمان، شیوه توزیع مزایا، قراردادهای داخلی کارابازار باشرکای تجاری و پلتفرمهای همکار، اطلاعات محرمانه تجاری محسوب شده و خارج از این قرارداد است و افشای آنهاتوسط کاربر بدون مجوز کتبی کارابازار، مجاز نمیباشد.</p><p>۵-۲. کاربر حق استناد به عدم درج جزئیات ساختار کسبوکار در این قرارداد را بهعنوان دلیل بطلان، فسخ یاادعای فریب علیه کارابازار از خود سلب مینماید.</p>" +
|
||||||
"<p class='section-title'>ماده ۶ - کد تأیید یکبارمصرف (OTP) و شناسه قرارداد</p><p>۶-۱. به منظور تأیید هویت کاربر و جایگزین کردن امضا در محیط غیرحضوری، برای این قرارداد یک شناسه یکتا(GUID) تولید شده و از طریق سامانه کارابازار، کد تأیید یکبارمصرف (OTP) مرتبط با آن برای کاربر ارسالمیگردد.</p>" +
|
"<p class='section-title'>ماده ۶ - کد تأیید یکبارمصرف (OTP) و شناسه قرارداد</p><p>۶-۱. به منظور تأیید هویت کاربر و جایگزین کردن امضا در محیط غیرحضوری، برای این قرارداد یک شناسه یکتا(GUID) تولید شده و از طریق سامانه کارابازار، کد تأیید یکبارمصرف (OTP) مرتبط با آن برای کاربر ارسالمیگردد.</p>" +
|
||||||
"<p>۶-۲. کاربر با وارد کردن صحیح کد تأیید (OTP) در سامانه، اقرار مینماید که:</p><ul><li>مالک شماره تماس ثبتشده در این قرارداد است یا با اجازه قانونی مالک از آن استفاده میکند،</li><li>از محتوای قرارداد بهطور کامل آگاه است و آن را بدون ابهام و با رضایت کامل میپذیرد،</li><li>شناسه یکتا و OTP ارسالشده، معادل امضای الکترونیکی معتبر وی بر روی این قرارداد است.</li></ul>" +
|
"<p>۶-۲. کاربر با وارد کردن صحیح کد تأیید (OTP) در سامانه، اقرار مینماید که:</p><ul><li>مالک شماره تماس ثبتشده در این قرارداد است یا با اجازه قانونی مالک از آن استفاده میکند،</li><li>از محتوای قرارداد بهطور کامل آگاه است و آن را بدون ابهام و با رضایت کامل میپذیرد،</li><li>شناسه یکتا و OTP ارسالشده، معادل امضای الکترونیکی معتبر وی بر روی این قرارداد است.</li></ul>" +
|
||||||
"<p class='section-title'>ماده ۷ - مدت، اصلاح و حاکمیت قانون</p>" +
|
"<p class='section-title'>ماده ۷ - مدت، اصلاح و حاکمیت قانون</p>" +
|
||||||
@@ -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">
|
||||||
@@ -19,9 +21,11 @@
|
|||||||
else if (_addresses.Count == 0)
|
else if (_addresses.Count == 0)
|
||||||
{
|
{
|
||||||
<MudAlert Severity="Severity.Warning">
|
<MudAlert Severity="Severity.Warning">
|
||||||
هیچ آدرسی ثبت نشده است. لطفاً از بخش پروفایل آدرس خود را اضافه کنید.
|
هیچ آدرسی ثبت نشده است. میتوانید همینجا آدرس جدید اضافه کنید.
|
||||||
</MudAlert>
|
</MudAlert>
|
||||||
<MudButton Class="mt-2" Variant="Variant.Outlined" Href="/profile/addresses">افزودن آدرس</MudButton>
|
<MudButton Class="mt-2" Variant="Variant.Outlined" Color="Color.Primary"
|
||||||
|
StartIcon="@Icons.Material.Filled.Add"
|
||||||
|
OnClick="OpenAddAddressDialog">افزودن آدرس</MudButton>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -29,7 +33,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">
|
||||||
@@ -38,13 +42,25 @@
|
|||||||
<MudText Typo="Typo.subtitle2">@address.Title</MudText>
|
<MudText Typo="Typo.subtitle2">@address.Title</MudText>
|
||||||
<MudText Typo="Typo.body2" Class="mud-text-secondary">@address.Address</MudText>
|
<MudText Typo="Typo.body2" Class="mud-text-secondary">@address.Address</MudText>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
@if (address.IsDefault)
|
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
|
||||||
{
|
@if (address.IsDefault)
|
||||||
<MudChip T="string" Color="Color.Success" Variant="Variant.Outlined">پیشفرض</MudChip>
|
{
|
||||||
}
|
<MudChip T="string" Color="Color.Success" Variant="Variant.Outlined" Size="Size.Small">پیشفرض</MudChip>
|
||||||
|
}
|
||||||
|
<span @onclick:stopPropagation="true">
|
||||||
|
<MudIconButton Icon="@Icons.Material.Filled.Edit"
|
||||||
|
Size="Size.Small"
|
||||||
|
Color="Color.Primary"
|
||||||
|
aria-label="ویرایش آدرس"
|
||||||
|
OnClick="@(() => OpenEditAddressDialog(address))" />
|
||||||
|
</span>
|
||||||
|
</MudStack>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
</MudPaper>
|
</MudPaper>
|
||||||
}
|
}
|
||||||
|
<MudButton Class="mt-2" Variant="Variant.Text" Color="Color.Primary"
|
||||||
|
StartIcon="@Icons.Material.Filled.Add"
|
||||||
|
OnClick="OpenAddAddressDialog">افزودن آدرس جدید</MudButton>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
}
|
}
|
||||||
</MudPaper>
|
</MudPaper>
|
||||||
@@ -83,8 +99,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 +133,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,11 @@
|
|||||||
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.Pages.Profile.Components;
|
||||||
|
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 +16,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 +48,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 +56,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 +64,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();
|
||||||
@@ -64,12 +73,14 @@ public partial class CheckoutSummary : ComponentBase
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
_addresses = new();
|
_addresses = new();
|
||||||
|
_selectedAddress = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Snackbar.Add($"خطا در بارگذاری آدرسها: {ex.Message}", Severity.Error);
|
Snackbar.Add($"خطا در بارگذاری آدرسها: {ex.Message}", Severity.Error);
|
||||||
_addresses = new();
|
_addresses = new();
|
||||||
|
_selectedAddress = null;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -78,6 +89,30 @@ public partial class CheckoutSummary : ComponentBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task OpenAddAddressDialog()
|
||||||
|
{
|
||||||
|
var dialog = await DialogService.ShowAsync<AddAddressDialog>("افزودن آدرس جدید");
|
||||||
|
var result = await dialog.Result;
|
||||||
|
if (result is { Canceled: false })
|
||||||
|
{
|
||||||
|
await AuthService.RefreshTokenAsync();
|
||||||
|
await LoadAddresses();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OpenEditAddressDialog(CustomerAddressModel address)
|
||||||
|
{
|
||||||
|
var dialog = await DialogService.ShowAsync<EditAddressDialog>("ویرایش آدرس", new DialogParameters<EditAddressDialog>
|
||||||
|
{
|
||||||
|
{ x => x.Model, address }
|
||||||
|
});
|
||||||
|
var result = await dialog.Result;
|
||||||
|
if (result is { Canceled: false })
|
||||||
|
{
|
||||||
|
await LoadAddresses();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task PlaceOrder()
|
private async Task PlaceOrder()
|
||||||
{
|
{
|
||||||
if (!CanPlaceOrder || _selectedAddress is null)
|
if (!CanPlaceOrder || _selectedAddress is null)
|
||||||
@@ -86,11 +121,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 +139,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('/');
|
||||||
}
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user