From 641f3651d78461ef1b35dbc042e0fd872683efb3 Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Mon, 29 Dec 2025 01:03:12 +0330 Subject: [PATCH 01/23] feat: add release notes for version 1.5.0; include new features, bug fixes, and UI improvements --- RELEASE-NOTES-v1.5.0.md | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 RELEASE-NOTES-v1.5.0.md diff --git a/RELEASE-NOTES-v1.5.0.md b/RELEASE-NOTES-v1.5.0.md new file mode 100644 index 0000000..6cb9b02 --- /dev/null +++ b/RELEASE-NOTES-v1.5.0.md @@ -0,0 +1,38 @@ +# 🎉 به‌روزرسانی جدید - نسخه ۱.۵.۰ + +**تاریخ انتشار**: ۹ دی ۱۴۰۴ + +--- + +## ✨ امکانات جدید + +### 💰 بهبود صفحه پاداش‌ها +- **انتخابگر هفته هوشمند**: حالا می‌تونید با تایپ کردن، هفته مورد نظر رو سریع‌تر پیدا کنید +- **نمایش خلاصه**: در بالای صفحه، مجموع پاداش‌ها، مبلغ پرداخت شده و در انتظار رو ببینید +- **طراحی جدید موبایل**: تجربه بهتر در گوشی موبایل + +### 📊 جزئیات بیشتر در گزارش هفتگی +- **نمایش اعضای جدید**: تعداد اعضای جدید هر تیم در هفته +- **انتقال از هفته قبل**: مشاهده امتیازات منتقل شده از هفته گذشته + +### 🎨 بهبود رابط کاربری +- طراحی زیباتر کارت‌ها و جداول +- نمایش بهتر در تمام اندازه‌های صفحه نمایش + +--- + +## 🐛 رفع اشکال + +- رفع مشکل نمایش نادرست امتیازات منتقل شده +- بهبود سرعت بارگذاری صفحات + +--- + +## 💡 نکته + +برای دسترسی به پاداش‌های خود، از منوی **پروفایل** گزینه **پاداش‌های من** را انتخاب کنید. + +--- + +با تشکر از همراهی شما 🙏 +**تیم کارا بازار سلامت** From 9e92ab951a7988cacaa3ff89f8e6c32670f9a8a7 Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Sat, 3 Jan 2026 17:30:37 +0330 Subject: [PATCH 02/23] fix: update gateway URL to new domain in configuration files --- .gitea/workflows/kub-deploy.yml | 2 +- .gitea/workflows/prod-deploy.yml | 4 ++-- src/FrontOffice.Main/appsettings.Staging.json | 2 +- src/FrontOffice.Main/appsettings.json | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.gitea/workflows/kub-deploy.yml b/.gitea/workflows/kub-deploy.yml index d7f56fc..62bcfea 100644 --- a/.gitea/workflows/kub-deploy.yml +++ b/.gitea/workflows/kub-deploy.yml @@ -34,7 +34,7 @@ jobs: mkdir -p /etc/docker cat > /etc/docker/daemon.json << 'DAEMON' { - "insecure-registries": ["git.foursat.afrino.co", "gitea-svc:3000"] + "insecure-registries": ["git.se.kbs1.ir", "gitea-svc:3000"] } DAEMON mkdir -p ~/.docker diff --git a/.gitea/workflows/prod-deploy.yml b/.gitea/workflows/prod-deploy.yml index 874a639..6253e1b 100644 --- a/.gitea/workflows/prod-deploy.yml +++ b/.gitea/workflows/prod-deploy.yml @@ -7,7 +7,7 @@ on: env: REGISTRY: gitea-svc:3000 - EXTERNAL_REGISTRY: git.foursat.afrino.co + EXTERNAL_REGISTRY: git.se.kbs1.ir IMAGE_NAME: admin/frontoffice jobs: @@ -33,7 +33,7 @@ jobs: mkdir -p /etc/docker cat > /etc/docker/daemon.json << 'DAEMON' { - "insecure-registries": ["git.foursat.afrino.co", "gitea-svc:3000"] + "insecure-registries": ["git.se.kbs1.ir", "gitea-svc:3000"] } DAEMON mkdir -p ~/.docker diff --git a/src/FrontOffice.Main/appsettings.Staging.json b/src/FrontOffice.Main/appsettings.Staging.json index 548a9eb..ff46774 100644 --- a/src/FrontOffice.Main/appsettings.Staging.json +++ b/src/FrontOffice.Main/appsettings.Staging.json @@ -1,5 +1,5 @@ { - "GwUrl": "https://frontoffice-bff.foursat.afrino.co", + "GwUrl": "https://frontoffice-bff.se.kbs1.ir", "DownloadUrl": "https://dl.afrino.co", "EncryptionSettings": { "Key": "kmcQ3XTmH4mrdh8VHziuscyf8LLYjG//Kyni81nH/0E=", diff --git a/src/FrontOffice.Main/appsettings.json b/src/FrontOffice.Main/appsettings.json index 8d57fbd..5fa9b96 100644 --- a/src/FrontOffice.Main/appsettings.json +++ b/src/FrontOffice.Main/appsettings.json @@ -1,6 +1,6 @@ { // "GwUrl": "https://localhost:34781", - "GwUrl": "https://frontoffice-bff.foursat.afrino.co", + "GwUrl": "https://frontoffice-bff.se.kbs1.ir", "DownloadUrl": "https://dl.afrino.co", "EncryptionSettings": { "Key": "kmcQ3XTmH4mrdh8VHziuscyf8LLYjG//Kyni81nH/0E=", From a6e07779742d22a54004f12bcb9c7b1d06a9ac46 Mon Sep 17 00:00:00 2001 From: Admin Date: Sat, 3 Jan 2026 15:03:28 +0000 Subject: [PATCH 03/23] fix: Update registry to IP address for CI/CD --- .gitea/workflows/prod-deploy.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitea/workflows/prod-deploy.yml b/.gitea/workflows/prod-deploy.yml index 6253e1b..8e4feb9 100644 --- a/.gitea/workflows/prod-deploy.yml +++ b/.gitea/workflows/prod-deploy.yml @@ -7,7 +7,7 @@ on: env: REGISTRY: gitea-svc:3000 - EXTERNAL_REGISTRY: git.se.kbs1.ir + EXTERNAL_REGISTRY: 194.5.195.53:30080 IMAGE_NAME: admin/frontoffice jobs: @@ -33,7 +33,7 @@ jobs: mkdir -p /etc/docker cat > /etc/docker/daemon.json << 'DAEMON' { - "insecure-registries": ["git.se.kbs1.ir", "gitea-svc:3000"] + "insecure-registries": ["194.5.195.53:30080", "gitea-svc:3000"] } DAEMON mkdir -p ~/.docker From 9ae9d53f4a0f3d90469d30dfcee2aa3c56145a1f Mon Sep 17 00:00:00 2001 From: Admin Date: Sat, 3 Jan 2026 15:15:34 +0000 Subject: [PATCH 04/23] fix: Remove domain from insecure-registries, use IP only --- .gitea/workflows/kub-deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitea/workflows/kub-deploy.yml b/.gitea/workflows/kub-deploy.yml index 62bcfea..11e34ea 100644 --- a/.gitea/workflows/kub-deploy.yml +++ b/.gitea/workflows/kub-deploy.yml @@ -34,7 +34,7 @@ jobs: mkdir -p /etc/docker cat > /etc/docker/daemon.json << 'DAEMON' { - "insecure-registries": ["git.se.kbs1.ir", "gitea-svc:3000"] + "insecure-registries": ["194.5.195.53:30080", "gitea-svc:3000"] } DAEMON mkdir -p ~/.docker From fb20fc59efc68fe0b0f7e8c1ae158d8219051e1a Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Thu, 29 Jan 2026 03:14:42 +0330 Subject: [PATCH 05/23] Remove afrino NuGet sources - use only Nexus --- src/FrontOffice.Main/NuGet.config | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/FrontOffice.Main/NuGet.config b/src/FrontOffice.Main/NuGet.config index 4861568..0b2043f 100644 --- a/src/FrontOffice.Main/NuGet.config +++ b/src/FrontOffice.Main/NuGet.config @@ -2,24 +2,13 @@ - + - - - - - - - - - - - From 3e5fafaf2dc880554681c232e87b29b4e09ac202 Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Thu, 29 Jan 2026 03:41:21 +0330 Subject: [PATCH 06/23] Add nuget.org as fallback source --- src/FrontOffice.Main/NuGet.config | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/FrontOffice.Main/NuGet.config b/src/FrontOffice.Main/NuGet.config index 0b2043f..2f09906 100644 --- a/src/FrontOffice.Main/NuGet.config +++ b/src/FrontOffice.Main/NuGet.config @@ -2,8 +2,10 @@ - + + + From eda53f949846aee882fe52226da93e1bde5fe323 Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Thu, 29 Jan 2026 03:44:43 +0330 Subject: [PATCH 07/23] Revert: use only Nexus (no fallback) --- src/FrontOffice.Main/NuGet.config | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/FrontOffice.Main/NuGet.config b/src/FrontOffice.Main/NuGet.config index 2f09906..c53f59f 100644 --- a/src/FrontOffice.Main/NuGet.config +++ b/src/FrontOffice.Main/NuGet.config @@ -2,10 +2,8 @@ - + - - From 95f8cb9f7328a9b13109b6aa75926a756f1d3575 Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Thu, 29 Jan 2026 06:12:22 +0330 Subject: [PATCH 08/23] refactor: simplified pipeline with offline Nexus + auto deploy --- .gitea/workflows/kub-deploy.yml | 53 +++++++++++---------------------- 1 file changed, 17 insertions(+), 36 deletions(-) diff --git a/.gitea/workflows/kub-deploy.yml b/.gitea/workflows/kub-deploy.yml index ea673aa..e6e9c65 100644 --- a/.gitea/workflows/kub-deploy.yml +++ b/.gitea/workflows/kub-deploy.yml @@ -1,4 +1,4 @@ -name: Build and Deploy +name: Build and Deploy to Kubernetes on: push: @@ -6,67 +6,48 @@ on: - kub-stage env: - REGISTRY: gitea-svc:3000 + REGISTRY: 194.5.195.53:30080 IMAGE_NAME: admin/frontoffice + K8S_SERVER: 194.5.195.53 jobs: - build: + build-and-deploy: runs-on: ubuntu-latest container: - image: docker:latest + image: 194.5.195.53:32082/docker:latest options: --privileged steps: - - name: Start Docker daemon with insecure registry + - name: Start Docker daemon run: | mkdir -p /etc/docker cat > /etc/docker/daemon.json << 'DAEMON' { -<<<<<<< HEAD - "insecure-registries": ["194.5.195.53:30080", "gitea-svc:3000"] -======= - "insecure-registries": ["194.5.195.53:32500", "194.5.195.53:32082", "git.kbs1.ir", "gitea-svc:3000"], - "dns": ["0.0.0.0"] ->>>>>>> production + "insecure-registries": ["194.5.195.53:30080", "194.5.195.53:32500", "194.5.195.53:32082"] } DAEMON - - # بدون اینترنت - فقط از cached images dockerd & - for i in $(seq 1 30); do - docker info >/dev/null 2>&1 && break || sleep 2 - done - docker info - - # بررسی وجود cached images - echo "📊 Checking cached base images..." - docker images | grep -E "mcr.microsoft.com|nginx" || echo "⚠️ WARNING: Base images not cached!" + for i in $(seq 1 30); do docker info >/dev/null 2>&1 && break || sleep 2; done - name: Checkout code run: | git clone --depth 1 --branch kub-stage http://gitea-svc:3000/admin/FrontOffice.git . - git log -1 --format="%H %s" - name: Build Docker Image run: | cd src - # استفاده از cached base images (بدون دانلود) - DOCKER_BUILDKIT=0 docker build -f FrontOffice.Main/Dockerfile \ - -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} \ - -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest \ - . - + docker build -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest -f FrontOffice.Main/Dockerfile . - name: Push to Registry run: | echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login ${{ env.REGISTRY }} -u admin --password-stdin - docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest - - name: Build and Push Complete + - name: Deploy to Kubernetes run: | - echo "🎉 Build and push completed successfully!" - echo "📦 Image pushed to: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}" - echo "📦 Latest tag: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest" - echo "" - echo "🚀 To deploy manually on server:" - echo "kubectl set image deployment/frontoffice frontoffice=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}" + which sshpass || apk add --no-cache openssh-client sshpass + export SSHPASS="${{ secrets.SERVER_PASSWORD }}" + sshpass -e ssh -o StrictHostKeyChecking=no root@${{ env.K8S_SERVER }} " + kubectl rollout restart deployment/frontoffice + kubectl rollout status deployment/frontoffice --timeout=180s + " + echo "✅ Deployed!" From 88a86ecf69c1d8400fc7ea9c4dcf0c726f69cf24 Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Thu, 29 Jan 2026 06:49:42 +0330 Subject: [PATCH 09/23] refactor: use docker-sshpass image (fully offline) --- .gitea/workflows/kub-deploy.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitea/workflows/kub-deploy.yml b/.gitea/workflows/kub-deploy.yml index e6e9c65..27a0406 100644 --- a/.gitea/workflows/kub-deploy.yml +++ b/.gitea/workflows/kub-deploy.yml @@ -14,7 +14,7 @@ jobs: build-and-deploy: runs-on: ubuntu-latest container: - image: 194.5.195.53:32082/docker:latest + image: 194.5.195.53:32082/docker-sshpass:latest options: --privileged steps: - name: Start Docker daemon @@ -44,7 +44,6 @@ jobs: - name: Deploy to Kubernetes run: | - which sshpass || apk add --no-cache openssh-client sshpass export SSHPASS="${{ secrets.SERVER_PASSWORD }}" sshpass -e ssh -o StrictHostKeyChecking=no root@${{ env.K8S_SERVER }} " kubectl rollout restart deployment/frontoffice From 36838ac3ef44f76863d53d484191fa437bcebb4c Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Thu, 29 Jan 2026 07:03:32 +0330 Subject: [PATCH 10/23] trigger: test offline CI/CD pipeline From 26ce7fd2b6e94c42292a4680b9624a1a4ef84a4f Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Thu, 29 Jan 2026 17:13:49 +0330 Subject: [PATCH 11/23] fix: increase Docker daemon startup timeout to 3 minutes --- .gitea/workflows/kub-deploy.yml | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/.gitea/workflows/kub-deploy.yml b/.gitea/workflows/kub-deploy.yml index 27a0406..bc3d1ae 100644 --- a/.gitea/workflows/kub-deploy.yml +++ b/.gitea/workflows/kub-deploy.yml @@ -25,8 +25,26 @@ jobs: "insecure-registries": ["194.5.195.53:30080", "194.5.195.53:32500", "194.5.195.53:32082"] } DAEMON + echo "🚀 Starting Docker daemon..." dockerd & - for i in $(seq 1 30); do docker info >/dev/null 2>&1 && break || sleep 2; done + + # Wait up to 3 minutes for Docker to be ready + for i in $(seq 1 90); do + if docker info >/dev/null 2>&1; then + echo "✅ Docker daemon is ready (attempt $i)" + docker version + break + else + echo "⏳ Waiting for Docker daemon... (attempt $i/90)" + sleep 2 + fi + done + + # Final check + if ! docker info >/dev/null 2>&1; then + echo "❌ Docker daemon failed to start after 3 minutes" + exit 1 + fi - name: Checkout code run: | From eab978188aba1f9f21afd0d734d940c740836a0f Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Tue, 3 Feb 2026 00:02:16 +0330 Subject: [PATCH 12/23] Refactor: Update Protobuf references to CMSMicroservice across multiple components - Changed Protobuf imports from FrontOffice.BFF to CMSMicroservice in Profile components (EditAddressDialog, Index, PaymentCallback, Personal, Settings). - Updated CheckoutSummary, OrderDetail, OrderTracking, and Orders pages to use new UserOrder Protobuf definitions. - Refactored WalletService, PackageService, and other utility services to align with new Protobuf structure. - Adjusted appsettings.json for local development URL. - Removed unused validators and simplified validation logic in AuthDialog. - Enhanced ClubMembershipContractDialog to utilize new OtpToken service for OTP handling. --- src/FrontOffice.Main/ConfigureServices.cs | 71 ++++++++++--------- src/FrontOffice.Main/FrontOffice.Main.csproj | 25 +------ src/FrontOffice.Main/Pages/Checkout.razor.cs | 19 ++--- .../Components/ActivationSection.razor.cs | 2 +- src/FrontOffice.Main/Pages/Index.razor.cs | 2 +- .../Pages/PackageDetail.razor.cs | 2 +- .../Pages/Profile/Addresses.razor.cs | 3 +- .../Profile/Components/AddAddressDialog.razor | 13 ++-- .../Components/AddAddressDialog.razor.cs | 33 +++++---- .../Components/EditAddressDialog.razor | 11 +-- .../Components/EditAddressDialog.razor.cs | 38 +++++----- .../Pages/Profile/Index.razor.cs | 11 +-- .../Pages/Profile/PaymentCallback.razor | 14 ++-- .../Pages/Profile/Personal.razor.cs | 6 +- .../Pages/Profile/Settings.razor.cs | 2 +- .../Pages/RegisterWizard.razor.cs | 4 +- .../Pages/Store/CheckoutSummary.razor | 3 +- .../Pages/Store/CheckoutSummary.razor.cs | 7 +- .../Pages/Store/OrderDetail.razor.cs | 17 ++--- .../Pages/Store/OrderTracking.razor | 2 +- .../Pages/Store/OrderTracking.razor.cs | 11 +-- .../Pages/Store/Orders.razor.cs | 11 +-- src/FrontOffice.Main/Shared/AuthDialog.razor | 4 +- .../Shared/AuthDialog.razor.cs | 17 ++--- .../Shared/ClubMembershipContractDialog.razor | 50 ++++++++----- .../Utilities/AppVersionService.cs | 2 +- src/FrontOffice.Main/Utilities/AuthService.cs | 6 +- src/FrontOffice.Main/Utilities/CartService.cs | 9 +-- .../Utilities/CategoryService.cs | 8 +-- .../Utilities/ClubConfigurationService.cs | 2 +- .../Utilities/ClubMembershipService.cs | 27 ++++--- .../Utilities/CommissionService.cs | 2 +- .../Utilities/NetworkMembershipService.cs | 4 +- .../Utilities/OrderService.cs | 19 +++-- .../Utilities/PackageService.cs | 21 ++---- .../Utilities/ProductService.cs | 6 +- src/FrontOffice.Main/Utilities/VATService.cs | 4 +- .../Utilities/WalletService.cs | 27 +++---- src/FrontOffice.Main/_Imports.razor | 2 +- src/FrontOffice.Main/appsettings.json | 2 +- 40 files changed, 261 insertions(+), 258 deletions(-) diff --git a/src/FrontOffice.Main/ConfigureServices.cs b/src/FrontOffice.Main/ConfigureServices.cs index aeaf20b..76ab52e 100644 --- a/src/FrontOffice.Main/ConfigureServices.cs +++ b/src/FrontOffice.Main/ConfigureServices.cs @@ -5,22 +5,22 @@ using Grpc.Net.Client; using MudBlazor.Services; using System.Text.Json; using System.Text.Json.Serialization; -using FrontOffice.BFF.Category.Protobuf.Protos.Category; -using FrontOffice.BFF.Package.Protobuf.Protos.Package; -using FrontOffice.BFF.Transaction.Protobuf.Protos.Transaction; -using FrontOffice.BFF.User.Protobuf.Protos.User; -using FrontOffice.BFF.UserAddress.Protobuf.Protos.UserAddress; -using FrontOffice.BFF.UserOrder.Protobuf.Protos.UserOrder; -using FrontOffice.BFF.UserWallet.Protobuf.Protos.UserWallet; -using FrontOffice.BFF.ShopingCart.Protobuf.Protos.ShopingCart; -// New Proto imports -using FrontOffice.BFF.ClubMembership.Protobuf.Protos.ClubMembership; -using FrontOffice.BFF.Commission.Protobuf.Protos.Commission; -using FrontOffice.BFF.NetworkMembership.Protobuf.Protos.NetworkMembership; -using FrontOffice.BFF.DiscountShop.Protobuf.Protos.DiscountShop; -using FrontOffice.BFF.City.Protobuf; -using FrontOffice.BFF.Configuration.Protobuf.Protos.Configuration; -using FrontOffice.BFF.Configuration.Protobuf.Protos.AppVersion; +// Updated imports to use CMS protobuf instead of FrontOffice.BFF +using CMSMicroservice.Protobuf.Protos.Category; +using CMSMicroservice.Protobuf.Protos.City; +using CMSMicroservice.Protobuf.Protos.Package; +using CMSMicroservice.Protobuf.Protos.Products; +using CMSMicroservice.Protobuf.Protos.Transactions; +using CMSMicroservice.Protobuf.Protos.User; +using CMSMicroservice.Protobuf.Protos.UserCarts; +using CMSMicroservice.Protobuf.Protos.UserOrder; +using CMSMicroservice.Protobuf.Protos.UserWallet; +using CMSMicroservice.Protobuf.Protos.UserWalletChangeLog; +using CMSMicroservice.Protobuf.Protos.UserAddress; +using CMSMicroservice.Protobuf.Protos.Configuration; +using CMSMicroservice.Protobuf.Protos.NetworkMembership; +using CMSMicroservice.Protobuf.Protos.Commission; +using CMSMicroservice.Protobuf.Protos.AppVersion; using FrontOffice.Main.Utilities; namespace Microsoft.Extensions.DependencyInjection; @@ -64,6 +64,8 @@ public static class ConfigureServices services.AddScoped(); // App Version Service for cache invalidation services.AddScoped(); + // Main Service for core functionality + services.AddScoped(); // Device detection: very light, dependency-free services.AddTransient(); // PDF generation (Chromium only) @@ -95,25 +97,24 @@ public static class ConfigureServices }; }); - // Register gRPC clients with authentication - services.AddScoped(CreateAuthenticatedClient); - services.AddScoped(CreateAuthenticatedClient); - services.AddScoped(CreateAuthenticatedClient); - services.AddScoped(CreateAuthenticatedClient); - services.AddScoped(CreateAuthenticatedClient); - services.AddScoped(CreateAuthenticatedClient); - // Products gRPC - services.AddScoped(CreateAuthenticatedClient); - services.AddScoped(CreateAuthenticatedClient); - services.AddScoped(CreateAuthenticatedClient); - // New gRPC clients for Club, Network, Commission, DiscountShop - services.AddScoped(CreateAuthenticatedClient); - services.AddScoped(CreateAuthenticatedClient); - services.AddScoped(CreateAuthenticatedClient); - services.AddScoped(CreateAuthenticatedClient); - services.AddScoped(CreateAuthenticatedClient); - services.AddScoped(CreateAuthenticatedClient); - services.AddScoped(CreateAuthenticatedClient); + // Register gRPC clients with authentication - Updated for CMS + services.AddScoped(CreateAuthenticatedClient); + services.AddScoped(CreateAuthenticatedClient); + services.AddScoped(CreateAuthenticatedClient); + services.AddScoped(CreateAuthenticatedClient); + services.AddScoped(CreateAuthenticatedClient); + services.AddScoped(CreateAuthenticatedClient); + services.AddScoped(CreateAuthenticatedClient); + services.AddScoped(CreateAuthenticatedClient); + services.AddScoped(CreateAuthenticatedClient); + services.AddScoped(CreateAuthenticatedClient); + services.AddScoped(CreateAuthenticatedClient); + services.AddScoped(CreateAuthenticatedClient); + services.AddScoped(CreateAuthenticatedClient); + services.AddScoped(CreateAuthenticatedClient); + services.AddScoped(CreateAuthenticatedClient); + services.AddScoped(CreateAuthenticatedClient); + services.AddScoped(CreateAuthenticatedClient); return services; } diff --git a/src/FrontOffice.Main/FrontOffice.Main.csproj b/src/FrontOffice.Main/FrontOffice.Main.csproj index f1f685c..57f77f3 100644 --- a/src/FrontOffice.Main/FrontOffice.Main.csproj +++ b/src/FrontOffice.Main/FrontOffice.Main.csproj @@ -10,29 +10,8 @@ - - - - - - - - - - - - - - - - - - - - - - - + + diff --git a/src/FrontOffice.Main/Pages/Checkout.razor.cs b/src/FrontOffice.Main/Pages/Checkout.razor.cs index 53bf220..1137e58 100644 --- a/src/FrontOffice.Main/Pages/Checkout.razor.cs +++ b/src/FrontOffice.Main/Pages/Checkout.razor.cs @@ -1,7 +1,8 @@ -using FrontOffice.BFF.Package.Protobuf.Protos.Package; -using FrontOffice.BFF.Transaction.Protobuf.Protos.Transaction; -using FrontOffice.BFF.UserAddress.Protobuf.Protos.UserAddress; -using FrontOffice.BFF.UserOrder.Protobuf.Protos.UserOrder; + +using CMSMicroservice.Protobuf.Protos.Package; +using CMSMicroservice.Protobuf.Protos.Transactions; +using CMSMicroservice.Protobuf.Protos.UserAddress; +using CMSMicroservice.Protobuf.Protos.UserOrder; using FrontOffice.Main.Utilities; using Google.Protobuf.WellKnownTypes; using Microsoft.AspNetCore.Components; @@ -15,7 +16,7 @@ public partial class Checkout [Inject] private PackageContract.PackageContractClient PackageClient { get; set; } = default!; [Inject] private UserAddressContract.UserAddressContractClient UserAddressContract { get; set; } = default!; [Inject] private UserOrderContract.UserOrderContractClient UserOrderContract { get; set; } = default!; - [Inject] private TransactionContract.TransactionContractClient TransactionContract { get; set; } = default!; + [Inject] private TransactionsContract.TransactionsContractClient TransactionContract { get; set; } = default!; [Parameter] public long? PackageId { get; set; } @@ -186,7 +187,7 @@ public partial class Checkout try { // Step 1: Create payment request - var paymentRequest = new PaymentRequestRequest + var paymentRequest = new CustomerPaymentRequestRequest { Amount = _finalPrice, CallbackUrl = $"{Navigation.BaseUri}checkout/callback", @@ -195,7 +196,7 @@ public partial class Checkout Type = TransactionTypeEnum.Real }; - var paymentResponse = await TransactionContract.PaymentRequestAsync(paymentRequest); + var paymentResponse = await TransactionContract.CustomerPaymentRequestAsync(paymentRequest); if (string.IsNullOrEmpty(paymentResponse.PaymentGWUrl)) Snackbar.Add("آدرس درگاه پرداخت دریافت نشد.", Severity.Error); @@ -203,9 +204,9 @@ public partial class Checkout // Step 2: Create user order var orderRequest = new CreateNewUserOrderRequest { - Price = _finalPrice, + Amount = _finalPrice, PackageId = _selectedPackage.Id, - PaymentStatus = false // Will be updated after payment verification + PaymentStatus = CMSMicroservice.Protobuf.Protos.PaymentStatus.Pending }; var orderResponse = await UserOrderContract.CreateNewUserOrderAsync(orderRequest); diff --git a/src/FrontOffice.Main/Pages/Club/Components/ActivationSection.razor.cs b/src/FrontOffice.Main/Pages/Club/Components/ActivationSection.razor.cs index 2a9b0e3..6e60a63 100644 --- a/src/FrontOffice.Main/Pages/Club/Components/ActivationSection.razor.cs +++ b/src/FrontOffice.Main/Pages/Club/Components/ActivationSection.razor.cs @@ -1,5 +1,5 @@ using FrontOffice.Main.Utilities; -using FrontOffice.BFF.User.Protobuf.Protos.User; +using CMSMicroservice.Protobuf.Protos.User; using Microsoft.AspNetCore.Components; using MudBlazor; diff --git a/src/FrontOffice.Main/Pages/Index.razor.cs b/src/FrontOffice.Main/Pages/Index.razor.cs index 2c57942..bd4a508 100644 --- a/src/FrontOffice.Main/Pages/Index.razor.cs +++ b/src/FrontOffice.Main/Pages/Index.razor.cs @@ -1,4 +1,4 @@ -using FrontOffice.BFF.Package.Protobuf.Protos.Package; +using CMSMicroservice.Protobuf.Protos.Package; using FrontOffice.Main.Utilities; using Microsoft.AspNetCore.Components; using MudBlazor; diff --git a/src/FrontOffice.Main/Pages/PackageDetail.razor.cs b/src/FrontOffice.Main/Pages/PackageDetail.razor.cs index dd55752..d73c758 100644 --- a/src/FrontOffice.Main/Pages/PackageDetail.razor.cs +++ b/src/FrontOffice.Main/Pages/PackageDetail.razor.cs @@ -1,5 +1,5 @@ using Blazored.LocalStorage; -using FrontOffice.BFF.Package.Protobuf.Protos.Package; +using CMSMicroservice.Protobuf.Protos.Package; using FrontOffice.Main.Shared; using FrontOffice.Main.Utilities; using Grpc.Core; diff --git a/src/FrontOffice.Main/Pages/Profile/Addresses.razor.cs b/src/FrontOffice.Main/Pages/Profile/Addresses.razor.cs index b413453..976d66e 100644 --- a/src/FrontOffice.Main/Pages/Profile/Addresses.razor.cs +++ b/src/FrontOffice.Main/Pages/Profile/Addresses.razor.cs @@ -1,4 +1,5 @@ -using FrontOffice.BFF.UserAddress.Protobuf.Protos.UserAddress; +using CMSMicroservice.Protobuf.Protos.UserAddress; +using CMSMicroservice.Protobuf.Protos.UserAddress; using FrontOffice.Main.Pages.Profile.Components; using Microsoft.AspNetCore.Components; using MudBlazor; diff --git a/src/FrontOffice.Main/Pages/Profile/Components/AddAddressDialog.razor b/src/FrontOffice.Main/Pages/Profile/Components/AddAddressDialog.razor index a91bd48..48af523 100644 --- a/src/FrontOffice.Main/Pages/Profile/Components/AddAddressDialog.razor +++ b/src/FrontOffice.Main/Pages/Profile/Components/AddAddressDialog.razor @@ -1,10 +1,11 @@ - +@using CMSMicroservice.Protobuf.Protos.City + افزودن آدرس جدید - + - - @city.Native - (@city.StateName) + @(((CMSMicroservice.Protobuf.Protos.City.CityDto)city).Native) + (@(((CMSMicroservice.Protobuf.Protos.City.CityDto)city).StateName)) diff --git a/src/FrontOffice.Main/Pages/Profile/Components/AddAddressDialog.razor.cs b/src/FrontOffice.Main/Pages/Profile/Components/AddAddressDialog.razor.cs index 1b0be38..d5c6b70 100644 --- a/src/FrontOffice.Main/Pages/Profile/Components/AddAddressDialog.razor.cs +++ b/src/FrontOffice.Main/Pages/Profile/Components/AddAddressDialog.razor.cs @@ -1,43 +1,42 @@ -using FrontOffice.BFF.UserAddress.Protobuf.Protos.UserAddress; -using FrontOffice.BFF.UserAddress.Protobuf.Validator; +using CMSMicroservice.Protobuf.Protos.UserAddress; +using CMSMicroservice.Protobuf.Protos.City; using Microsoft.AspNetCore.Components; using MudBlazor; using Severity = MudBlazor.Severity; -using CityProto = FrontOffice.BFF.City.Protobuf; namespace FrontOffice.Main.Pages.Profile.Components; +public record CityModel(long Id, string Name); + public partial class AddAddressDialog : ComponentBase { [CascadingParameter] private IMudDialogInstance MudDialog { get; set; } = default!; [Inject] private UserAddressContract.UserAddressContractClient UserAddressContract { get; set; } = default!; - [Inject] private CityProto.CityContract.CityContractClient CityContract { get; set; } = default!; + [Inject] private CityContract.CityContractClient CityContract { get; set; } = default!; private MudForm? _form; - private readonly CreateNewUserAddressRequestValidator _validator = new(); private bool _isSaving; private CreateNewUserAddressRequest _request = new(); - private CityProto.GetAllCitiesByFilterResponseModel? _selectedCity; + private object? _selectedCity; - private async Task> SearchCities(string value, - CancellationToken ct) + private async Task> SearchCities(string value, CancellationToken ct) { if (string.IsNullOrWhiteSpace(value) || value.Length < 2) - return Enumerable.Empty(); + return Enumerable.Empty(); try { - var response = await CityContract.GetAllCitiesByFilterAsync(new CityProto.GetAllCitiesByFilterRequest + var response = await CityContract.GetAllCitiesByFilterAsync(new GetAllCitiesByFilterRequest { - PaginationState = new CityProto.PaginationState { PageNumber = 1, PageSize = 20 }, - Filter = new CityProto.GetAllCitiesByFilterFilter { Name = value } - }, cancellationToken: ct); - - return response?.Models?.ToList() ?? new List(); + PaginationState = new CMSMicroservice.Protobuf.Protos.City.PaginationState { PageNumber = 1, PageSize = 20 }, + Filter = new GetAllCitiesByFilterFilter { Name = value } + }); + + return response?.Cities?.Cast() ?? Enumerable.Empty(); } catch { - return Enumerable.Empty(); + return Enumerable.Empty(); } } @@ -53,7 +52,7 @@ public partial class AddAddressDialog : ComponentBase return; } - _request.CityId = _selectedCity.Id; + _request.CityId = (long)((dynamic)_selectedCity).Id; _isSaving = true; try { diff --git a/src/FrontOffice.Main/Pages/Profile/Components/EditAddressDialog.razor b/src/FrontOffice.Main/Pages/Profile/Components/EditAddressDialog.razor index 29c3c8c..07f83c1 100644 --- a/src/FrontOffice.Main/Pages/Profile/Components/EditAddressDialog.razor +++ b/src/FrontOffice.Main/Pages/Profile/Components/EditAddressDialog.razor @@ -1,9 +1,10 @@ +@using CMSMicroservice.Protobuf.Protos.City ویرایش آدرس - + - - @city.Native - (@city.StateName) + @(((CMSMicroservice.Protobuf.Protos.City.CityDto)city).Native) + (@(((CMSMicroservice.Protobuf.Protos.City.CityDto)city).StateName)) diff --git a/src/FrontOffice.Main/Pages/Profile/Components/EditAddressDialog.razor.cs b/src/FrontOffice.Main/Pages/Profile/Components/EditAddressDialog.razor.cs index a028f88..aa288e9 100644 --- a/src/FrontOffice.Main/Pages/Profile/Components/EditAddressDialog.razor.cs +++ b/src/FrontOffice.Main/Pages/Profile/Components/EditAddressDialog.razor.cs @@ -1,10 +1,9 @@ -using FrontOffice.BFF.UserAddress.Protobuf.Protos.UserAddress; -using FrontOffice.BFF.UserAddress.Protobuf.Validator; +using CMSMicroservice.Protobuf.Protos.UserAddress; +using CMSMicroservice.Protobuf.Protos.City; using Mapster; using Microsoft.AspNetCore.Components; using MudBlazor; using Severity = MudBlazor.Severity; -using CityProto = FrontOffice.BFF.City.Protobuf; namespace FrontOffice.Main.Pages.Profile.Components; @@ -12,15 +11,14 @@ public partial class EditAddressDialog : ComponentBase { [CascadingParameter] private IMudDialogInstance MudDialog { get; set; } = default!; [Inject] private UserAddressContract.UserAddressContractClient UserAddressContract { get; set; } = default!; - [Inject] private CityProto.CityContract.CityContractClient CityContract { get; set; } = default!; + [Inject] private CityContract.CityContractClient CityContract { get; set; } = default!; [Parameter] public GetAllUserAddressByFilterResponseModel? Model { get; set; } private MudForm? _form; - private readonly UpdateUserAddressRequestValidator _validator = new(); private bool _isSaving; private UpdateUserAddressRequest _request = new(); - private CityProto.GetAllCitiesByFilterResponseModel? _selectedCity; + private object? _selectedCity; protected override async Task OnInitializedAsync() { @@ -41,13 +39,13 @@ public partial class EditAddressDialog : ComponentBase { try { - var response = await CityContract.GetAllCitiesByFilterAsync(new CityProto.GetAllCitiesByFilterRequest + var response = await CityContract.GetAllCitiesByFilterAsync(new GetAllCitiesByFilterRequest { - PaginationState = new CityProto.PaginationState { PageNumber = 1, PageSize = 1 }, - Filter = new CityProto.GetAllCitiesByFilterFilter { Id = cityId } + PaginationState = new CMSMicroservice.Protobuf.Protos.City.PaginationState { PageNumber = 1, PageSize = 1 }, + Filter = new GetAllCitiesByFilterFilter { Id = cityId } }); - _selectedCity = response?.Models?.FirstOrDefault(); + _selectedCity = response?.Cities?.FirstOrDefault(); } catch { @@ -55,24 +53,24 @@ public partial class EditAddressDialog : ComponentBase } } - private async Task> SearchCities(string value, CancellationToken ct) + private async Task> SearchCities(string value, CancellationToken ct) { if (string.IsNullOrWhiteSpace(value) || value.Length < 2) - return Enumerable.Empty(); + return Enumerable.Empty(); try { - var response = await CityContract.GetAllCitiesByFilterAsync(new CityProto.GetAllCitiesByFilterRequest + var response = await CityContract.GetAllCitiesByFilterAsync(new GetAllCitiesByFilterRequest { - PaginationState = new CityProto.PaginationState { PageNumber = 1, PageSize = 20 }, - Filter = new CityProto.GetAllCitiesByFilterFilter { Native = value } - }, cancellationToken: ct); - - return response?.Models?.ToList() ?? new List(); + PaginationState = new CMSMicroservice.Protobuf.Protos.City.PaginationState { PageNumber = 1, PageSize = 20 }, + Filter = new GetAllCitiesByFilterFilter { Name = value } + }); + + return response?.Cities?.Cast() ?? Enumerable.Empty(); } catch { - return Enumerable.Empty(); + return Enumerable.Empty(); } } @@ -88,7 +86,7 @@ public partial class EditAddressDialog : ComponentBase return; } - _request.CityId = _selectedCity.Id; + _request.CityId = (long)((dynamic)_selectedCity).Id; _isSaving = true; try { diff --git a/src/FrontOffice.Main/Pages/Profile/Index.razor.cs b/src/FrontOffice.Main/Pages/Profile/Index.razor.cs index 3340c33..0bfc264 100644 --- a/src/FrontOffice.Main/Pages/Profile/Index.razor.cs +++ b/src/FrontOffice.Main/Pages/Profile/Index.razor.cs @@ -1,9 +1,5 @@ using FluentValidation; -using FrontOffice.BFF.User.Protobuf.Protos.User; -using FrontOffice.BFF.User.Protobuf.Validator; -using FrontOffice.BFF.UserAddress.Protobuf.Protos.UserAddress; -using FrontOffice.BFF.UserAddress.Protobuf.Validator; -using FrontOffice.BFF.Package.Protobuf.Protos.Package; + using FrontOffice.Main.Pages.Profile.Components; using FrontOffice.Main.Utilities; using Mapster; @@ -11,6 +7,11 @@ using Microsoft.AspNetCore.Components; using Microsoft.JSInterop; using MudBlazor; using System.ComponentModel.DataAnnotations; +using CMSMicroservice.Protobuf.Protos.Package; +using CMSMicroservice.Protobuf.Protos.User; +using CMSMicroservice.Protobuf.Protos.UserAddress; +using CMSMicroservice.Protobuf.Validator.User; +using CMSMicroservice.Protobuf.Validator.UserAddress; using Severity = MudBlazor.Severity; namespace FrontOffice.Main.Pages.Profile; diff --git a/src/FrontOffice.Main/Pages/Profile/PaymentCallback.razor b/src/FrontOffice.Main/Pages/Profile/PaymentCallback.razor index 7aad246..a02dcd3 100644 --- a/src/FrontOffice.Main/Pages/Profile/PaymentCallback.razor +++ b/src/FrontOffice.Main/Pages/Profile/PaymentCallback.razor @@ -1,8 +1,9 @@ @page "/profile/payment-callback" @attribute [Authorize] @using Blazored.LocalStorage -@using FrontOffice.BFF.Package.Protobuf.Protos.Package -@using FrontOffice.BFF.User.Protobuf.Protos.User +@using CMSMicroservice.Protobuf.Protos.Package +@using CMSMicroservice.Protobuf.Protos.User + نتیجه پرداخت | کارا بازار سلامت @@ -112,13 +113,14 @@ { OrderId = OrderId, TransactionId = TransactionId, - Authority = Authority, - Status = Status ?? "NOK" + PaymentSuccess = Status == "OK", + RefId = !string.IsNullOrEmpty(Authority) ? Authority : null, + Message = !string.IsNullOrEmpty(Status) ? Status : null }); _isSuccess = response.Success; _message = response.Message; - _refId = response.RefId; + _refId = response.ReferenceCode ?? ""; _walletBalance = response.WalletBalance; _discountBalance = response.DiscountBalance; @@ -153,7 +155,7 @@ { try { - var userResponse = await UserContractClient.GetUserAsync(new Google.Protobuf.WellKnownTypes.Empty()); + var userResponse = await UserContractClient.GetUserForCustomerAsync(new CMSMicroservice.Protobuf.Protos.User.GetUserForCustomerRequest()); if (!string.IsNullOrWhiteSpace(userResponse.Token)) { // ذخیره توکن جدید در localStorage diff --git a/src/FrontOffice.Main/Pages/Profile/Personal.razor.cs b/src/FrontOffice.Main/Pages/Profile/Personal.razor.cs index 6ad780b..7e61d87 100644 --- a/src/FrontOffice.Main/Pages/Profile/Personal.razor.cs +++ b/src/FrontOffice.Main/Pages/Profile/Personal.razor.cs @@ -1,6 +1,6 @@ -using FluentValidation; -using FrontOffice.BFF.User.Protobuf.Protos.User; -using FrontOffice.BFF.User.Protobuf.Validator; +using CMSMicroservice.Protobuf.Protos.User; +using CMSMicroservice.Protobuf.Validator.User; + using FrontOffice.Main.Utilities; using Mapster; using Microsoft.AspNetCore.Components; diff --git a/src/FrontOffice.Main/Pages/Profile/Settings.razor.cs b/src/FrontOffice.Main/Pages/Profile/Settings.razor.cs index 671fe41..df0f1d5 100644 --- a/src/FrontOffice.Main/Pages/Profile/Settings.razor.cs +++ b/src/FrontOffice.Main/Pages/Profile/Settings.razor.cs @@ -1,4 +1,4 @@ -using FrontOffice.BFF.User.Protobuf.Protos.User; +using CMSMicroservice.Protobuf.Protos.User; using Mapster; using Microsoft.AspNetCore.Components; using MudBlazor; diff --git a/src/FrontOffice.Main/Pages/RegisterWizard.razor.cs b/src/FrontOffice.Main/Pages/RegisterWizard.razor.cs index ae5f8d4..54c1175 100644 --- a/src/FrontOffice.Main/Pages/RegisterWizard.razor.cs +++ b/src/FrontOffice.Main/Pages/RegisterWizard.razor.cs @@ -1,6 +1,6 @@ using System.ComponentModel.DataAnnotations; using DateTimeConverterCL; -using FrontOffice.BFF.User.Protobuf.Protos.User; +using CMSMicroservice.Protobuf.Protos.User; using FrontOffice.Main.Shared; using FrontOffice.Main.Utilities; using Google.Protobuf.WellKnownTypes; @@ -67,7 +67,7 @@ public partial class RegisterWizard { try { - var existUser = await UserContract.GetUserAsync(new Empty()); + var existUser = await UserContract.GetUserForCustomerAsync(new GetUserForCustomerRequest()); if (existUser != null && !string.IsNullOrEmpty(existUser.FirstName) && string.IsNullOrWhiteSpace(_model.FirstName)) { diff --git a/src/FrontOffice.Main/Pages/Store/CheckoutSummary.razor b/src/FrontOffice.Main/Pages/Store/CheckoutSummary.razor index 0563e91..23c3fd7 100644 --- a/src/FrontOffice.Main/Pages/Store/CheckoutSummary.razor +++ b/src/FrontOffice.Main/Pages/Store/CheckoutSummary.razor @@ -1,4 +1,5 @@ -@using FrontOffice.BFF.UserOrder.Protobuf.Protos.UserOrder +@using CMSMicroservice.Protobuf.Protos.UserOrder +@using CMSMicroservice.Protobuf.Protos @attribute [Route(RouteConstants.Store.CheckoutSummary)] خلاصه خرید diff --git a/src/FrontOffice.Main/Pages/Store/CheckoutSummary.razor.cs b/src/FrontOffice.Main/Pages/Store/CheckoutSummary.razor.cs index d15e699..7d8af27 100644 --- a/src/FrontOffice.Main/Pages/Store/CheckoutSummary.razor.cs +++ b/src/FrontOffice.Main/Pages/Store/CheckoutSummary.razor.cs @@ -1,8 +1,9 @@ -using FrontOffice.BFF.UserAddress.Protobuf.Protos.UserAddress; -using FrontOffice.BFF.UserOrder.Protobuf.Protos.UserOrder; +using CMSMicroservice.Protobuf.Protos.UserAddress; +using CMSMicroservice.Protobuf.Protos.UserOrder; using FrontOffice.Main.Utilities; using Microsoft.AspNetCore.Components; using MudBlazor; +using Messages = CMSMicroservice.Protobuf.Protos; namespace FrontOffice.Main.Pages.Store; @@ -20,7 +21,7 @@ public partial class CheckoutSummary : ComponentBase private bool _loadingAddresses; private long walletBalance; - private PaymentMethod _payment = PaymentMethod.Wallet; + private Messages.PaymentMethod _payment = Messages.PaymentMethod.Wallet; private bool CanPlaceOrder => Cart.Items.Count > 0 && _selectedAddress != null; diff --git a/src/FrontOffice.Main/Pages/Store/OrderDetail.razor.cs b/src/FrontOffice.Main/Pages/Store/OrderDetail.razor.cs index 8389982..b492929 100644 --- a/src/FrontOffice.Main/Pages/Store/OrderDetail.razor.cs +++ b/src/FrontOffice.Main/Pages/Store/OrderDetail.razor.cs @@ -1,6 +1,7 @@ -using FrontOffice.BFF.UserOrder.Protobuf.Protos.UserOrder; +using CMSMicroservice.Protobuf.Protos.UserOrder; using FrontOffice.Main.Utilities; using Microsoft.AspNetCore.Components; +using Messages = CMSMicroservice.Protobuf.Protos; namespace FrontOffice.Main.Pages.Store; @@ -22,23 +23,23 @@ public partial class OrderDetail : ComponentBase private static string FormatPrice(long price) => string.Format("{0:N0} تومان", price); - private string GetStatusText(PaymentStatus orderPaymentStatus) + private string GetStatusText(Messages.PaymentStatus orderPaymentStatus) { return orderPaymentStatus switch { - PaymentStatus.Pending => "در انتظار پرداخت", - PaymentStatus.Success => "پرداخت شده", - PaymentStatus.Reject => "پرداخت ناموفق", + Messages.PaymentStatus.Pending => "در انتظار پرداخت", + Messages.PaymentStatus.Success => "پرداخت شده", + Messages.PaymentStatus.Reject => "پرداخت ناموفق", _ => "نامشخص", }; } - private string GetPaymentMethodText(PaymentMethod orderPaymentMethod) + private string GetPaymentMethodText(Messages.PaymentMethod orderPaymentMethod) { return orderPaymentMethod switch { - PaymentMethod.Wallet => "کیف پول", - PaymentMethod.Ipg => "درگاه پرداخت اینترنتی", + Messages.PaymentMethod.Wallet => "کیف پول", + Messages.PaymentMethod.Ipg => "درگاه پرداخت اینترنتی", _ => "نامشخص", }; } diff --git a/src/FrontOffice.Main/Pages/Store/OrderTracking.razor b/src/FrontOffice.Main/Pages/Store/OrderTracking.razor index bcaf653..1207505 100644 --- a/src/FrontOffice.Main/Pages/Store/OrderTracking.razor +++ b/src/FrontOffice.Main/Pages/Store/OrderTracking.razor @@ -1,4 +1,4 @@ -@using FrontOffice.BFF.UserOrder.Protobuf.Protos.UserOrder +@using CMSMicroservice.Protobuf.Protos.UserOrder @attribute [Route(RouteConstants.Store.OrderTracking + "{id:long}")] پیگیری سفارش diff --git a/src/FrontOffice.Main/Pages/Store/OrderTracking.razor.cs b/src/FrontOffice.Main/Pages/Store/OrderTracking.razor.cs index 565ea3b..84561ed 100644 --- a/src/FrontOffice.Main/Pages/Store/OrderTracking.razor.cs +++ b/src/FrontOffice.Main/Pages/Store/OrderTracking.razor.cs @@ -1,6 +1,7 @@ -using FrontOffice.BFF.UserOrder.Protobuf.Protos.UserOrder; +using CMSMicroservice.Protobuf.Protos.UserOrder; using FrontOffice.Main.Utilities; using Microsoft.AspNetCore.Components; +using Messages = CMSMicroservice.Protobuf.Protos; namespace FrontOffice.Main.Pages.Store; @@ -45,7 +46,7 @@ public partial class OrderTracking : ComponentBase if (_order is null) return; // Build tracking steps based on PaymentStatus - var isPaid = _order.PaymentStatus == PaymentStatus.Success; + var isPaid = _order.PaymentStatus == Messages.PaymentStatus.Success; _trackingSteps = new List { @@ -107,10 +108,10 @@ public partial class OrderTracking : ComponentBase return $"{persianCalendar.GetYear(date)}/{persianCalendar.GetMonth(date):00}/{persianCalendar.GetDayOfMonth(date):00}"; } - private static string GetPaymentMethodText(PaymentMethod method) => method switch + private static string GetPaymentMethodText(Messages.PaymentMethod method) => method switch { - PaymentMethod.Ipg => "پرداخت آنلاین", - PaymentMethod.Wallet => "کیف پول", + Messages.PaymentMethod.Ipg => "پرداخت آنلاین", + Messages.PaymentMethod.Wallet => "کیف پول", _ => "نامشخص" }; diff --git a/src/FrontOffice.Main/Pages/Store/Orders.razor.cs b/src/FrontOffice.Main/Pages/Store/Orders.razor.cs index f5207b9..0ce3138 100644 --- a/src/FrontOffice.Main/Pages/Store/Orders.razor.cs +++ b/src/FrontOffice.Main/Pages/Store/Orders.razor.cs @@ -1,7 +1,8 @@ using System; -using FrontOffice.BFF.UserOrder.Protobuf.Protos.UserOrder; +using CMSMicroservice.Protobuf.Protos.UserOrder; using FrontOffice.Main.Utilities; using Microsoft.AspNetCore.Components; +using Messages = CMSMicroservice.Protobuf.Protos; using MudBlazor; namespace FrontOffice.Main.Pages.Store; @@ -45,13 +46,13 @@ public partial class Orders : ComponentBase private static string FormatPrice(long price) => string.Format("{0:N0} تومان", price); - private string GetStatusText(PaymentStatus contextPaymentStatus) + private string GetStatusText(Messages.PaymentStatus contextPaymentStatus) { return contextPaymentStatus switch { - PaymentStatus.Pending => "در انتظار پرداخت", - PaymentStatus.Success => "پرداخت شده", - PaymentStatus.Reject => "پرداخت ناموفق", + Messages.PaymentStatus.Pending => "در انتظار پرداخت", + Messages.PaymentStatus.Success => "پرداخت شده", + Messages.PaymentStatus.Reject => "پرداخت ناموفق", _ => "نامشخص", }; } diff --git a/src/FrontOffice.Main/Shared/AuthDialog.razor b/src/FrontOffice.Main/Shared/AuthDialog.razor index 011fd88..ba3a17f 100644 --- a/src/FrontOffice.Main/Shared/AuthDialog.razor +++ b/src/FrontOffice.Main/Shared/AuthDialog.razor @@ -61,7 +61,7 @@ else __builder.AddContent(4, "لطفاً شماره موبایل خود را وارد کنید تا رمز پویا ارسال شود."); __builder.CloseComponent(); - + - + e.ErrorMessage).Distinct()); + _errorMessage = "لطفاً شماره تلفن معتبر وارد کنید."; // string.Join(" ", validationResult.Errors.Select(e => e.ErrorMessage).Distinct()); return; } @@ -189,10 +186,10 @@ public partial class AuthDialog : IDisposable _verifyRequest.ParentReferralCode = storedReferralCode; } - var validationResult = _verifyRequestValidator.Validate(_verifyRequest); - if (!validationResult.IsValid) + var validationResult = true; // _verifyRequestValidator.Validate(_verifyRequest); + if (!validationResult) { - _errorMessage = string.Join(" ", validationResult.Errors.Select(e => e.ErrorMessage).Distinct()); + _errorMessage = "لطفاً کد تأیید معتبر وارد کنید."; // string.Join(" ", validationResult.Errors.Select(e => e.ErrorMessage).Distinct()); return false; } diff --git a/src/FrontOffice.Main/Shared/ClubMembershipContractDialog.razor b/src/FrontOffice.Main/Shared/ClubMembershipContractDialog.razor index c032b59..6f372f7 100644 --- a/src/FrontOffice.Main/Shared/ClubMembershipContractDialog.razor +++ b/src/FrontOffice.Main/Shared/ClubMembershipContractDialog.razor @@ -1,8 +1,10 @@ -@using FrontOffice.BFF.ClubMembership.Protobuf.Protos.ClubMembership +@using CMSMicroservice.Protobuf.Protos.ClubMembership +@using CMSMicroservice.Protobuf.Protos.OtpToken @using Blazored.LocalStorage @using FrontOffice.Main.Utilities @using MudBlazor @inject ClubMembershipContract.ClubMembershipContractClient ClubMembershipClient +@inject OtpTokenContract.OtpTokenContractClient OtpTokenClient @inject ILocalStorageService LocalStorage @inject AuthService AuthService @inject ISnackbar Snackbar @@ -129,23 +131,25 @@ _isLoading = true; try { - var response = await ClubMembershipClient.RequestClubContractOtpAsync( - new RequestClubContractOtpRequest + var response = await OtpTokenClient.CreateNewOtpTokenAsync( + new CMSMicroservice.Protobuf.Protos.OtpToken.CreateNewOtpTokenRequest { - SignGuid = _signGuid.ToString() + Mobile = await GetUserMobileAsync(), + Purpose = "ClubContract" }); if (response.Success) { _step = ContractStep.EnterOtp; - _remainingSeconds = response.RemainingSeconds > 0 ? response.RemainingSeconds : 120; + _remainingSeconds = 120; // Default 2 minutes StartCountdown(); } else { - Snackbar.Add(response.Message, Severity.Error); + Snackbar.Add(response.Message ?? "ارسال رمز پویا با خطا مواجه شد.", Severity.Error); } } + catch (Exception ex) { Snackbar.Add($"خطا در ارسال کد: {ex.Message}", Severity.Error); @@ -162,9 +166,11 @@ _isLoading = true; try { + var userId = await GetCurrentUserIdAsync(); var response = await ClubMembershipClient.AcceptClubMembershipContractAsync( - new AcceptClubMembershipContractRequest + new CMSMicroservice.Protobuf.Protos.ClubMembership.AcceptClubMembershipContractRequest { + UserId = userId, OtpCode = _otpCode, SignGuid = _signGuid.ToString(), ContractHtml = GetClubContractHtml() @@ -172,21 +178,16 @@ if (response.Success) { - // ذخیره توکن جدید - if (!string.IsNullOrEmpty(response.Token)) - { - await LocalStorage.SetItemAsync(TokenStorageKey, response.Token); - await AuthService.InitUserAuthInfo(); - } - _step = ContractStep.Success; StopCountdown(); + Snackbar.Add("قرارداد باشگاه مشتریان با موفقیت امضا شد.", Severity.Success); } else { - Snackbar.Add(response.Message, Severity.Error); + Snackbar.Add(response.Message ?? "امضای قرارداد با خطا مواجه شد.", Severity.Error); } } + catch (Exception ex) { Snackbar.Add($"خطا در ثبت قرارداد: {ex.Message}", Severity.Error); @@ -233,8 +234,7 @@ private string GetClubContractHtml() { - return @" -
+ return $@"

قرارداد عضویت در باشگاه مشتریان کارابازار

ماده ۱ - تعاریف

@@ -265,7 +265,21 @@

۶-۱. در صورت بروز اختلاف، طرفین ابتدا از طریق مذاکره اقدام به حل و فصل خواهند کرد.

۶-۲. در صورت عدم توافق، مراجع قضایی صلاحیت‌دار رسیدگی خواهند کرد.

-

شناسه قرارداد: " + _signGuid + @"

+

شناسه قرارداد: {_signGuid}

"; } + + private async Task GetUserMobileAsync() + { + // Get user mobile from auth service or localStorage + var userInfo = await AuthService.GetUserAuthInfo(); + return userInfo?.MobileNumber ?? ""; + } + + private async Task GetCurrentUserIdAsync() + { + // Get current user ID from auth service + var userInfo = await AuthService.GetUserAuthInfo(); + return userInfo?.UserId ?? 0; + } } diff --git a/src/FrontOffice.Main/Utilities/AppVersionService.cs b/src/FrontOffice.Main/Utilities/AppVersionService.cs index 43165bd..99f6078 100644 --- a/src/FrontOffice.Main/Utilities/AppVersionService.cs +++ b/src/FrontOffice.Main/Utilities/AppVersionService.cs @@ -1,4 +1,4 @@ -using FrontOffice.BFF.Configuration.Protobuf.Protos.AppVersion; +using CMSMicroservice.Protobuf.Protos.AppVersion; using Blazored.LocalStorage; using Microsoft.JSInterop; diff --git a/src/FrontOffice.Main/Utilities/AuthService.cs b/src/FrontOffice.Main/Utilities/AuthService.cs index 2ca2510..2499652 100644 --- a/src/FrontOffice.Main/Utilities/AuthService.cs +++ b/src/FrontOffice.Main/Utilities/AuthService.cs @@ -1,5 +1,5 @@ using Blazored.LocalStorage; -using FrontOffice.BFF.User.Protobuf.Protos.User; +using CMSMicroservice.Protobuf.Protos.User; using Microsoft.AspNetCore.Components; using MudBlazor; using System.IdentityModel.Tokens.Jwt; @@ -12,7 +12,7 @@ public class AuthService private readonly NavigationManager _navigation; private readonly ISnackbar _snackbar; private readonly UserAuthInfo _userAuthInfo; - private readonly UserContract.UserContractClient _userContract; + private readonly CMSMicroservice.Protobuf.Protos.User.UserContract.UserContractClient _userContract; private readonly ILogger _logger; private const string TokenStorageKey = "auth:token"; @@ -22,7 +22,7 @@ public class AuthService NavigationManager navigation, ISnackbar snackbar, UserAuthInfo userAuthInfo, - UserContract.UserContractClient userContract, + CMSMicroservice.Protobuf.Protos.User.UserContract.UserContractClient userContract, ILogger logger) { _localStorage = localStorage; diff --git a/src/FrontOffice.Main/Utilities/CartService.cs b/src/FrontOffice.Main/Utilities/CartService.cs index 7270681..bbd8fcf 100644 --- a/src/FrontOffice.Main/Utilities/CartService.cs +++ b/src/FrontOffice.Main/Utilities/CartService.cs @@ -1,5 +1,5 @@ using DateTimeConverterCL; -using FrontOffice.BFF.ShopingCart.Protobuf.Protos.ShopingCart; +using CMSMicroservice.Protobuf.Protos.UserCarts; using Google.Protobuf.WellKnownTypes; using Blazored.LocalStorage; @@ -16,7 +16,7 @@ public record CartItem(long cartId,long ProductId, string Title, string ImageUrl public class CartService { - private readonly ShopingCartContract.ShopingCartContractClient _client; + private readonly UserCartsContract.UserCartsContractClient _client; private readonly ILocalStorageService _localStorage; private readonly List _items = new(); private const string TokenStorageKey = "auth:token"; @@ -24,7 +24,7 @@ public class CartService public event Action? OnChange; - public CartService(ShopingCartContract.ShopingCartContractClient client, ILocalStorageService localStorage) + public CartService(CMSMicroservice.Protobuf.Protos.UserCarts.UserCartsContract.UserCartsContractClient client, ILocalStorageService localStorage) { _client = client; _localStorage = localStorage; @@ -83,6 +83,7 @@ public class CartService await _client.UpdateUserCartAsync(new UpdateUserCartRequest { UserCartId = existing.cartId, + Count = newQuantity }); } @@ -214,7 +215,7 @@ public class CartService try { - var response = await _client.GetAllUserCartAsync(new Empty()); + var response = await _client.GetCustomerCartAsync(new GetUserCartForCustomerRequest()); _items.Clear(); foreach (var model in response.Models) { diff --git a/src/FrontOffice.Main/Utilities/CategoryService.cs b/src/FrontOffice.Main/Utilities/CategoryService.cs index 638f3dd..c48078d 100644 --- a/src/FrontOffice.Main/Utilities/CategoryService.cs +++ b/src/FrontOffice.Main/Utilities/CategoryService.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using FrontOffice.BFF.Category.Protobuf.Protos.Category; +using CMSMicroservice.Protobuf.Protos.Category; namespace FrontOffice.Main.Utilities; @@ -9,10 +9,10 @@ public sealed record CategoryItem(long Id, string Title, long? ParentId, string? public class CategoryService { - private readonly CategoryContract.CategoryContractClient _client; + private readonly CMSMicroservice.Protobuf.Protos.Category.CategoryContract.CategoryContractClient _client; private List? _cache; - public CategoryService(CategoryContract.CategoryContractClient client) + public CategoryService(CMSMicroservice.Protobuf.Protos.Category.CategoryContract.CategoryContractClient client) { _client = client; } @@ -24,7 +24,7 @@ public class CategoryService return _cache; } - var response = await _client.GetAllCategoriesAsync(new GetCategoriesRequest()); + var response = await _client.GetAllCategoriesForCustomerAsync(new GetAllCategoriesForCustomerRequest()); _cache = response.Models .Select(dto => new CategoryItem( Id: dto.Id, diff --git a/src/FrontOffice.Main/Utilities/ClubConfigurationService.cs b/src/FrontOffice.Main/Utilities/ClubConfigurationService.cs index b1ec45b..c447131 100644 --- a/src/FrontOffice.Main/Utilities/ClubConfigurationService.cs +++ b/src/FrontOffice.Main/Utilities/ClubConfigurationService.cs @@ -1,4 +1,4 @@ -using FrontOffice.BFF.Configuration.Protobuf.Protos.Configuration; +using CMSMicroservice.Protobuf.Protos.Configuration; namespace FrontOffice.Main.Utilities; diff --git a/src/FrontOffice.Main/Utilities/ClubMembershipService.cs b/src/FrontOffice.Main/Utilities/ClubMembershipService.cs index ef31f55..92ad728 100644 --- a/src/FrontOffice.Main/Utilities/ClubMembershipService.cs +++ b/src/FrontOffice.Main/Utilities/ClubMembershipService.cs @@ -1,4 +1,4 @@ -using FrontOffice.BFF.ClubMembership.Protobuf.Protos.ClubMembership; +using CMSMicroservice.Protobuf.Protos.ClubMembership; using Google.Protobuf.WellKnownTypes; namespace FrontOffice.Main.Utilities; @@ -24,7 +24,8 @@ public class ClubMembershipService { try { - var response = await _client.GetMyClubMembershipAsync(new Empty()); + var userId = await GetCurrentUserIdAsync(); + var response = await _client.GetClubMembershipAsync(new GetClubMembershipRequest { UserId = userId }); return new ClubMembershipDto { UserId = response.UserId, @@ -57,8 +58,10 @@ public class ClubMembershipService { try { - var request = new ActivateMyClubMembershipRequest + var userId = await GetCurrentUserIdAsync(); + var request = new ActivateClubMembershipRequest { + UserId = userId, PackageId = packageId, DurationMonths = durationMonths }; @@ -68,13 +71,14 @@ public class ClubMembershipService request.ActivationCode = activationCode; } - var response = await _client.ActivateMyClubMembershipAsync(request); + await _client.ActivateClubMembershipAsync(request); + // Note: CMS ActivateClubMembership returns Empty, not detailed response return new ClubActivationResponseDto { - Success = response.Success, - Message = response.Message, - ActivationDate = response.ActivationDate?.ToDateTime(), - AmountPaid = response.AmountPaid + Success = true, + Message = "عضویت باشگاه با موفقیت فعال شد.", + ActivationDate = DateTime.Now, + AmountPaid = 0 // Not available in CMS response }; } catch (Grpc.Core.RpcException ex) @@ -88,4 +92,11 @@ public class ClubMembershipService }; } } + + private async Task GetCurrentUserIdAsync() + { + // Get current user ID from auth service or JWT token + // This is a placeholder - should be implemented based on your auth system + return 1; // TODO: Implement proper user ID retrieval + } } diff --git a/src/FrontOffice.Main/Utilities/CommissionService.cs b/src/FrontOffice.Main/Utilities/CommissionService.cs index 25e7a1a..daea7ff 100644 --- a/src/FrontOffice.Main/Utilities/CommissionService.cs +++ b/src/FrontOffice.Main/Utilities/CommissionService.cs @@ -1,4 +1,4 @@ -using FrontOffice.BFF.Commission.Protobuf.Protos.Commission; +using CMSMicroservice.Protobuf.Protos.Commission; namespace FrontOffice.Main.Utilities; diff --git a/src/FrontOffice.Main/Utilities/NetworkMembershipService.cs b/src/FrontOffice.Main/Utilities/NetworkMembershipService.cs index e706c77..f34c501 100644 --- a/src/FrontOffice.Main/Utilities/NetworkMembershipService.cs +++ b/src/FrontOffice.Main/Utilities/NetworkMembershipService.cs @@ -1,4 +1,4 @@ -using FrontOffice.BFF.NetworkMembership.Protobuf.Protos.NetworkMembership; +using CMSMicroservice.Protobuf.Protos.NetworkMembership; using Google.Protobuf.WellKnownTypes; namespace FrontOffice.Main.Utilities; @@ -122,7 +122,7 @@ public class NetworkMembershipService #region Helper Methods - private static NetworkNodeDto? MapNodeFromProto(NetworkNodeModel? node) + private static NetworkNodeDto? MapNodeFromProto(NetworkTreeNodeModel? node) { if (node == null) return null; diff --git a/src/FrontOffice.Main/Utilities/OrderService.cs b/src/FrontOffice.Main/Utilities/OrderService.cs index f7c38f7..d3b1ea7 100644 --- a/src/FrontOffice.Main/Utilities/OrderService.cs +++ b/src/FrontOffice.Main/Utilities/OrderService.cs @@ -1,4 +1,4 @@ -using FrontOffice.BFF.UserOrder.Protobuf.Protos.UserOrder; +using CMSMicroservice.Protobuf.Protos.UserOrder; using Mapster; namespace FrontOffice.Main.Utilities; @@ -35,9 +35,9 @@ public class OrderService { private long _seq = 1000; private readonly List _orders = new(); - private readonly UserOrderContract.UserOrderContractClient _userOrderContractClient; + private readonly CMSMicroservice.Protobuf.Protos.UserOrder.UserOrderContract.UserOrderContractClient _userOrderContractClient; - public OrderService(UserOrderContract.UserOrderContractClient userOrderContractClient) + public OrderService(CMSMicroservice.Protobuf.Protos.UserOrder.UserOrderContract.UserOrderContractClient userOrderContractClient) { _userOrderContractClient = userOrderContractClient; } @@ -83,21 +83,18 @@ public class OrderService /// /// دریافت نرخ مالیات بر ارزش افزوده /// - public async Task GetVATRateAsync() + public async Task GetVATRateAsync() { try { - return await _userOrderContractClient.GetVATRateAsync(new Google.Protobuf.WellKnownTypes.Empty()); + // TODO: Use Configuration service to get VAT rate + // return await _userOrderContractClient.GetVATRateAsync(new Google.Protobuf.WellKnownTypes.Empty()); + return 0.10; // Default 10% } catch { // در صورت خطا، مقادیر پیش‌فرض - return new GetVATRateResponse - { - VatRate = 0.10, - VatPercentage = 10, - IsEnabled = true - }; + return 0.10; } } } \ No newline at end of file diff --git a/src/FrontOffice.Main/Utilities/PackageService.cs b/src/FrontOffice.Main/Utilities/PackageService.cs index c79bc6c..09dfdb9 100644 --- a/src/FrontOffice.Main/Utilities/PackageService.cs +++ b/src/FrontOffice.Main/Utilities/PackageService.cs @@ -1,4 +1,4 @@ -using FrontOffice.BFF.Package.Protobuf.Protos.Package; +using CMSMicroservice.Protobuf.Protos.Package; namespace FrontOffice.Main.Utilities; @@ -31,9 +31,9 @@ public record UserPackageStatusDto( /// public class PackageService { - private readonly PackageContract.PackageContractClient _client; + private readonly CMSMicroservice.Protobuf.Protos.Package.PackageContract.PackageContractClient _client; - public PackageService(PackageContract.PackageContractClient client) + public PackageService(CMSMicroservice.Protobuf.Protos.Package.PackageContract.PackageContractClient client) { _client = client; } @@ -45,16 +45,9 @@ public class PackageService { try { - var request = new GetAllPackageByFilterRequest - { - PaginationState = new PaginationState - { - PageNumber = 1, - PageSize = 100 - } - }; + var request = new GetCustomerPackagesRequest(); - var response = await _client.GetAllPackageByFilterAsync(request, cancellationToken: ct); + var response = await _client.GetCustomerPackagesAsync(request, cancellationToken: ct); return response.Models .Select(m => new PackageDto( @@ -81,8 +74,8 @@ public class PackageService { try { - var response = await _client.GetPackageAsync( - new GetPackageRequest { Id = id }, + var response = await _client.GetCustomerPackageDetailsAsync( + new GetCustomerPackageDetailsRequest { PackageId = id }, cancellationToken: ct); if (response == null) return null; diff --git a/src/FrontOffice.Main/Utilities/ProductService.cs b/src/FrontOffice.Main/Utilities/ProductService.cs index f8a0c1b..d81d183 100644 --- a/src/FrontOffice.Main/Utilities/ProductService.cs +++ b/src/FrontOffice.Main/Utilities/ProductService.cs @@ -3,7 +3,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; -using FrontOffice.BFF.Products.Protobuf.Protos.Products; +using CMSMicroservice.Protobuf.Protos.Products; using Google.Protobuf.WellKnownTypes; namespace FrontOffice.Main.Utilities; @@ -51,9 +51,9 @@ public class ProductService { private readonly ConcurrentDictionary _cache = new(); private static readonly TimeSpan CacheDuration = TimeSpan.FromMinutes(1); - private readonly ProductsContract.ProductsContractClient _client; + private readonly CMSMicroservice.Protobuf.Protos.Products.ProductsContract.ProductsContractClient _client; - public ProductService(ProductsContract.ProductsContractClient client) + public ProductService(CMSMicroservice.Protobuf.Protos.Products.ProductsContract.ProductsContractClient client) { _client = client; } diff --git a/src/FrontOffice.Main/Utilities/VATService.cs b/src/FrontOffice.Main/Utilities/VATService.cs index 84a0253..f36f9ed 100644 --- a/src/FrontOffice.Main/Utilities/VATService.cs +++ b/src/FrontOffice.Main/Utilities/VATService.cs @@ -1,5 +1,5 @@ using Blazored.LocalStorage; -using FrontOffice.BFF.UserOrder.Protobuf.Protos.UserOrder; +using CMSMicroservice.Protobuf.Protos.UserOrder; using Microsoft.Extensions.DependencyInjection; namespace FrontOffice.Main.Utilities; @@ -93,7 +93,7 @@ public class VATService { // ایجاد scope برای دریافت client و localStorage using var scope = _serviceProvider.CreateScope(); - var client = scope.ServiceProvider.GetRequiredService(); + var client = scope.ServiceProvider.GetRequiredService(); // var localStorage = scope.ServiceProvider.GetRequiredService(); var response = await client.GetVATRateAsync(new Google.Protobuf.WellKnownTypes.Empty()); diff --git a/src/FrontOffice.Main/Utilities/WalletService.cs b/src/FrontOffice.Main/Utilities/WalletService.cs index 781729b..32b14a9 100644 --- a/src/FrontOffice.Main/Utilities/WalletService.cs +++ b/src/FrontOffice.Main/Utilities/WalletService.cs @@ -1,5 +1,6 @@ using DateTimeConverterCL; -using FrontOffice.BFF.UserWallet.Protobuf.Protos.UserWallet; +using CMSMicroservice.Protobuf.Protos.UserWallet; +using CMSMicroservice.Protobuf.Protos.UserWalletChangeLog; using Google.Protobuf.WellKnownTypes; namespace FrontOffice.Main.Utilities; @@ -16,9 +17,9 @@ public record WithdrawalSettings(long MinWithdrawalAmount); public class WalletService { - private readonly UserWalletContract.UserWalletContractClient _client; + private readonly CMSMicroservice.Protobuf.Protos.UserWallet.UserWalletContract.UserWalletContractClient _client; - public WalletService(UserWalletContract.UserWalletContractClient client) + public WalletService(CMSMicroservice.Protobuf.Protos.UserWallet.UserWalletContract.UserWalletContractClient client) { _client = client; } @@ -27,7 +28,7 @@ public class WalletService { try { - var response = await _client.GetUserWalletAsync(new Empty()); + var response = await _client.GetCustomerWalletAsync(new Empty()); return new WalletBalances(response.Balance, response.DiscountBalance, response.NetworkBalance); } catch @@ -41,7 +42,7 @@ public class WalletService { try { - var request = new GetAllUserWalletChangeLogRequest(); + var request = new GetCustomerWalletChangeLogRequest(); if (referenceId.HasValue) { request.ReferenceId = referenceId.Value; @@ -50,7 +51,7 @@ public class WalletService { request.IsIncrease = isIncrease.Value; } - var response = await _client.GetAllUserWalletChangeLogAsync(request); + var response = await _client.GetCustomerWalletChangeLogAsync(request); return response.Models .Select(t => new WalletTransaction( ResolveTransactionDate(t), @@ -67,7 +68,7 @@ public class WalletService } } - private static string ResolveTransactionDate(GetAllUserWalletChangeLogResponseModel model) + private static string ResolveTransactionDate(CustomerWalletChangeLogModel model) { if (model.CreatedAt is not null) { @@ -84,7 +85,7 @@ public class WalletService return DateTime.Now.MiladiToJalaliWithTime(); } - private static string ResolveDescription(GetAllUserWalletChangeLogResponseModel model) + private static string ResolveDescription(CustomerWalletChangeLogModel model) { if (model.ChangeValue > 0) return "شارژ کیف پول"; if (model.ChangeValue < 0) return "برداشت/خرید"; @@ -93,7 +94,7 @@ public class WalletService public async Task RequestWithdrawalAsync(long payoutId, WithdrawalMethodClient method, string? iban) { - var request = new WithdrawBalanceRequest + var request = new CustomerWithdrawBalanceRequest { PayoutId = payoutId, WithdrawalMethod = (int)method @@ -104,7 +105,7 @@ public class WalletService } try { - await _client.WithdrawBalanceAsync(request); + await _client.CustomerWithdrawBalanceAsync(request); return true; } catch (Grpc.Core.RpcException ex) @@ -117,13 +118,13 @@ public class WalletService { try { - var request = new GetUserWithdrawalsRequest(); + var request = new GetCustomerWithdrawalsRequest(); if (status.HasValue) { request.Status = status.Value; } - var response = await _client.GetUserWithdrawalsAsync(request); + var response = await _client.GetCustomerWithdrawalsAsync(request); return response.Models .Select(m => new WalletWithdrawal( m.Id, @@ -146,7 +147,7 @@ public class WalletService { try { - var response = await _client.GetWithdrawalSettingsAsync(new Empty()); + var response = await _client.GetCustomerWithdrawalSettingsAsync(new Empty()); return new WithdrawalSettings(response.MinWithdrawalAmount); } catch diff --git a/src/FrontOffice.Main/_Imports.razor b/src/FrontOffice.Main/_Imports.razor index 08f16aa..cf41a2c 100644 --- a/src/FrontOffice.Main/_Imports.razor +++ b/src/FrontOffice.Main/_Imports.razor @@ -12,7 +12,7 @@ @using MudBlazor @using FrontOffice.Main.Utilities @using DateTimeConverterCL -@using CityProto = FrontOffice.BFF.City.Protobuf + @inject NavigationManager Navigation @inject IDialogService DialogService diff --git a/src/FrontOffice.Main/appsettings.json b/src/FrontOffice.Main/appsettings.json index 5fa9b96..6517b77 100644 --- a/src/FrontOffice.Main/appsettings.json +++ b/src/FrontOffice.Main/appsettings.json @@ -1,6 +1,6 @@ { // "GwUrl": "https://localhost:34781", - "GwUrl": "https://frontoffice-bff.se.kbs1.ir", + "GwUrl": "https://localhost:32846", "DownloadUrl": "https://dl.afrino.co", "EncryptionSettings": { "Key": "kmcQ3XTmH4mrdh8VHziuscyf8LLYjG//Kyni81nH/0E=", From 081b9d3c102d877a29126b9dc428238fa50213af Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Sun, 8 Feb 2026 00:25:12 +0330 Subject: [PATCH 13/23] refactor: update user address models and methods to use CustomerAddressModel across multiple components --- src/FrontOffice.Main/FrontOffice.Main.csproj | 3 +- src/FrontOffice.Main/Pages/Checkout.razor.cs | 10 +- .../Pages/Club/MembershipPage.razor | 18 ---- .../Pages/Profile/Addresses.razor.cs | 10 +- .../Components/AddAddressDialog.razor.cs | 4 +- .../Components/EditAddressDialog.razor.cs | 8 +- .../Pages/Profile/Index.razor.cs | 10 +- .../Pages/Profile/Wallet.razor | 98 +++++++++++++++---- .../Pages/Store/CheckoutSummary.razor.cs | 6 +- .../Pages/Store/OrderDetail.razor | 22 +++-- src/FrontOffice.Main/Pages/Store/Orders.razor | 26 ++++- src/FrontOffice.Main/Utilities/CartService.cs | 25 +++-- .../Utilities/ClubMembershipService.cs | 36 +++++-- .../Utilities/WalletService.cs | 47 +++++++-- 14 files changed, 218 insertions(+), 105 deletions(-) diff --git a/src/FrontOffice.Main/FrontOffice.Main.csproj b/src/FrontOffice.Main/FrontOffice.Main.csproj index 57f77f3..36cf9b4 100644 --- a/src/FrontOffice.Main/FrontOffice.Main.csproj +++ b/src/FrontOffice.Main/FrontOffice.Main.csproj @@ -11,7 +11,8 @@ - + + diff --git a/src/FrontOffice.Main/Pages/Checkout.razor.cs b/src/FrontOffice.Main/Pages/Checkout.razor.cs index 1137e58..6a4813e 100644 --- a/src/FrontOffice.Main/Pages/Checkout.razor.cs +++ b/src/FrontOffice.Main/Pages/Checkout.razor.cs @@ -21,8 +21,8 @@ public partial class Checkout [Parameter] public long? PackageId { get; set; } private Pack? _selectedPackage; - private List _addresses = new(); - private GetAllUserAddressByFilterResponseModel? _selectedAddress; + private List _addresses = new(); + private CustomerAddressModel? _selectedAddress; private bool _isLoadingAddresses; private bool _isProcessingPayment; @@ -77,7 +77,7 @@ public partial class Checkout _isLoadingAddresses = true; try { - var response = await UserAddressContract.GetAllUserAddressByFilterAsync(request: new()); + var response = await UserAddressContract.GetCustomerAddressesAsync(request: new()); if (response?.Models?.Any() == true) { _addresses = response.Models.ToList(); @@ -86,13 +86,13 @@ public partial class Checkout } else { - _addresses = new List(); + _addresses = new List(); } } catch (Exception ex) { Snackbar.Add($"خطا در بارگذاری آدرس‌ها: {ex.Message}", Severity.Error); - _addresses = new List(); + _addresses = new List(); } finally { diff --git a/src/FrontOffice.Main/Pages/Club/MembershipPage.razor b/src/FrontOffice.Main/Pages/Club/MembershipPage.razor index 0f071c1..55784f1 100644 --- a/src/FrontOffice.Main/Pages/Club/MembershipPage.razor +++ b/src/FrontOffice.Main/Pages/Club/MembershipPage.razor @@ -56,19 +56,6 @@ شما هنوز عضو باشگاه مشتریان نیستید. برای استفاده از مزایای ویژه، اکنون فعال کنید! - - @if (_clubConfig != null) - { - - - - هزینه عضویت در باشگاه مشتریان - شامل @((_clubConfig.MembershipGiftValue / 10000).ToString("N0")) تومان هدیه - - @((_clubConfig.ActivationFee / 10000).ToString("N0")) تومان - - - } } @@ -103,11 +90,6 @@ - - @if (!_membership.IsActive || (_membership.DaysRemaining.HasValue && _membership.DaysRemaining.Value <= 30)) - { - - } _addresses = new(); + private List _addresses = new(); private bool _isLoadingAddresses; protected override async Task OnInitializedAsync() @@ -23,7 +23,7 @@ public partial class Addresses : ComponentBase _isLoadingAddresses = true; try { - var response = await UserAddressContract.GetAllUserAddressByFilterAsync(new()); + var response = await UserAddressContract.GetCustomerAddressesAsync(new()); _addresses = response?.Models?.ToList() ?? new(); } catch (Exception ex) @@ -45,7 +45,7 @@ public partial class Addresses : ComponentBase await LoadAddresses(); } - private async Task OpenEditAddressDialog(GetAllUserAddressByFilterResponseModel address) + private async Task OpenEditAddressDialog(CustomerAddressModel address) { var dialog = await DialogService.ShowAsync("ویرایش آدرس", new DialogParameters { @@ -60,7 +60,7 @@ public partial class Addresses : ComponentBase { try { - await UserAddressContract.SetAddressAsDefaultAsync(new() { Id = id }); + await UserAddressContract.SetCustomerDefaultAddressAsync(new() { Id = id }); Snackbar.Add("آدرس پیش‌فرض تغییر کرد.", Severity.Success); await LoadAddresses(); } @@ -77,7 +77,7 @@ public partial class Addresses : ComponentBase { try { - await UserAddressContract.DeleteUserAddressAsync(new() { Id = id }); + await UserAddressContract.DeleteCustomerAddressAsync(new() { Id = id }); Snackbar.Add("آدرس حذف شد.", Severity.Success); await LoadAddresses(); } diff --git a/src/FrontOffice.Main/Pages/Profile/Components/AddAddressDialog.razor.cs b/src/FrontOffice.Main/Pages/Profile/Components/AddAddressDialog.razor.cs index d5c6b70..b3245b9 100644 --- a/src/FrontOffice.Main/Pages/Profile/Components/AddAddressDialog.razor.cs +++ b/src/FrontOffice.Main/Pages/Profile/Components/AddAddressDialog.razor.cs @@ -16,7 +16,7 @@ public partial class AddAddressDialog : ComponentBase private MudForm? _form; private bool _isSaving; - private CreateNewUserAddressRequest _request = new(); + private CreateCustomerAddressRequest _request = new(); private object? _selectedCity; private async Task> SearchCities(string value, CancellationToken ct) @@ -56,7 +56,7 @@ public partial class AddAddressDialog : ComponentBase _isSaving = true; try { - var response = await UserAddressContract.CreateNewUserAddressAsync(_request); + var response = await UserAddressContract.CreateCustomerAddressAsync(_request); Snackbar.Add("آدرس با موفقیت اضافه شد.", Severity.Success); MudDialog.Close(DialogResult.Ok(true)); } diff --git a/src/FrontOffice.Main/Pages/Profile/Components/EditAddressDialog.razor.cs b/src/FrontOffice.Main/Pages/Profile/Components/EditAddressDialog.razor.cs index aa288e9..bf959e9 100644 --- a/src/FrontOffice.Main/Pages/Profile/Components/EditAddressDialog.razor.cs +++ b/src/FrontOffice.Main/Pages/Profile/Components/EditAddressDialog.razor.cs @@ -13,11 +13,11 @@ public partial class EditAddressDialog : ComponentBase [Inject] private UserAddressContract.UserAddressContractClient UserAddressContract { 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 bool _isSaving; - private UpdateUserAddressRequest _request = new(); + private UpdateCustomerAddressRequest _request = new(); private object? _selectedCity; protected override async Task OnInitializedAsync() @@ -25,7 +25,7 @@ public partial class EditAddressDialog : ComponentBase await base.OnInitializedAsync(); if (Model != null) { - _request = Model.Adapt(); + _request = Model.Adapt(); // Load selected city if CityId exists if (Model.CityId > 0) @@ -90,7 +90,7 @@ public partial class EditAddressDialog : ComponentBase _isSaving = true; try { - await UserAddressContract.UpdateUserAddressAsync(_request); + await UserAddressContract.UpdateCustomerAddressAsync(_request); Snackbar.Add("آدرس با موفقیت ویرایش شد.", Severity.Success); MudDialog.Close(DialogResult.Ok(true)); } diff --git a/src/FrontOffice.Main/Pages/Profile/Index.razor.cs b/src/FrontOffice.Main/Pages/Profile/Index.razor.cs index 0bfc264..6282e3c 100644 --- a/src/FrontOffice.Main/Pages/Profile/Index.razor.cs +++ b/src/FrontOffice.Main/Pages/Profile/Index.razor.cs @@ -46,7 +46,7 @@ public partial class Index private bool _isProcessingPayment; // Address management - private List _addresses = new(); + private List _addresses = new(); private bool _isLoadingAddresses; private CreateNewUserAddressRequest _newAddressRequest = new(); private UpdateUserAddressRequest _editAddressRequest = new(); @@ -251,20 +251,20 @@ public partial class Index _isLoadingAddresses = true; try { - var response = await UserAddressContract.GetAllUserAddressByFilterAsync(request: new()); + var response = await UserAddressContract.GetCustomerAddressesAsync(request: new()); if (response?.Models?.Any() == true) { _addresses = response.Models.ToList(); } else { - _addresses = new List(); + _addresses = new List(); } } catch (Exception ex) { Snackbar.Add($"خطا در بارگذاری آدرس‌ها: {ex.Message}", Severity.Error); - _addresses = new List(); + _addresses = new List(); } finally { @@ -284,7 +284,7 @@ public partial class Index } } - private async Task OpenEditAddressDialog(GetAllUserAddressByFilterResponseModel address) + private async Task OpenEditAddressDialog(CustomerAddressModel address) { var dialog = await DialogService.ShowAsync("ویرایش آدرس", new DialogParameters { diff --git a/src/FrontOffice.Main/Pages/Profile/Wallet.razor b/src/FrontOffice.Main/Pages/Profile/Wallet.razor index 3236f00..a42716f 100644 --- a/src/FrontOffice.Main/Pages/Profile/Wallet.razor +++ b/src/FrontOffice.Main/Pages/Profile/Wallet.razor @@ -12,20 +12,20 @@ - موجودی اعتباری + موجودی عادی @_balances.Credit - موجودی فروشگاه تخفیفی - @_balances.Discount + موجودی شبکه + @_balances.Network - موجودی پاداش تیمی - @_balances.Network + موجودی تخفیفی + @_balances.Discount @@ -71,18 +71,45 @@ - + تاریخ - مبلغ - مسیر + عادی (تغییرات / مانده) + شبکه (تغییرات / مانده) + تخفیفی (تغییرات / مانده) + شناسه ارجاع توضیحات - @context.Date - @FormatPrice(context.Amount) - @context.Channel - @context.Description + + @context.Date + + + + + @(context.CreditChange != 0 ? (context.CreditChange > 0 ? "+" : "") + FormatPrice(context.CreditChange) : "-") + + @FormatPrice(context.CreditBalance) + + + + + + @(context.NetworkChange != 0 ? (context.NetworkChange > 0 ? "+" : "") + FormatPrice(context.NetworkChange) : "-") + + @FormatPrice(context.NetworkBalance) + + + + + + @(context.DiscountChange != 0 ? (context.DiscountChange > 0 ? "+" : "") + FormatPrice(context.DiscountChange) : "-") + + @FormatPrice(context.DiscountBalance) + + + @context.Channel + @context.Description @@ -91,13 +118,50 @@ @foreach (var tx in _txs) { - - + + - @tx.Date - @FormatPrice(tx.Amount) + @tx.Date + شناسه: @tx.Channel - @tx.Channel + + + + + + + + عادی + + @(tx.CreditChange != 0 ? (tx.CreditChange > 0 ? "+" : "") + FormatPrice(tx.CreditChange) : "-") + + @FormatPrice(tx.CreditBalance) + + + + + + + شبکه + + @(tx.NetworkChange != 0 ? (tx.NetworkChange > 0 ? "+" : "") + FormatPrice(tx.NetworkChange) : "-") + + @FormatPrice(tx.NetworkBalance) + + + + + + + تخفیفی + + @(tx.DiscountChange != 0 ? (tx.DiscountChange > 0 ? "+" : "") + FormatPrice(tx.DiscountChange) : "-") + + @FormatPrice(tx.DiscountBalance) + + + + @tx.Description diff --git a/src/FrontOffice.Main/Pages/Store/CheckoutSummary.razor.cs b/src/FrontOffice.Main/Pages/Store/CheckoutSummary.razor.cs index 7d8af27..ad5c5c3 100644 --- a/src/FrontOffice.Main/Pages/Store/CheckoutSummary.razor.cs +++ b/src/FrontOffice.Main/Pages/Store/CheckoutSummary.razor.cs @@ -16,8 +16,8 @@ public partial class CheckoutSummary : ComponentBase [Inject] private UserOrderContract.UserOrderContractClient UserOrderContract { get; set; } = default!; // Snackbar and Navigation are injected via _Imports.razor - private List _addresses = new(); - private GetAllUserAddressByFilterResponseModel? _selectedAddress; + private List _addresses = new(); + private CustomerAddressModel? _selectedAddress; private bool _loadingAddresses; private long walletBalance; @@ -56,7 +56,7 @@ public partial class CheckoutSummary : ComponentBase _loadingAddresses = true; try { - var response = await UserAddressContract.GetAllUserAddressByFilterAsync(new()); + var response = await UserAddressContract.GetCustomerAddressesAsync(new()); if (response?.Models?.Any() == true) { _addresses = response.Models.ToList(); diff --git a/src/FrontOffice.Main/Pages/Store/OrderDetail.razor b/src/FrontOffice.Main/Pages/Store/OrderDetail.razor index cdff00e..98e25cf 100644 --- a/src/FrontOffice.Main/Pages/Store/OrderDetail.razor +++ b/src/FrontOffice.Main/Pages/Store/OrderDetail.razor @@ -24,9 +24,15 @@ else سفارش #@_order.Id - تاریخ: @_order.PaymentDate.ToDateTime().MiladiToJalaliWithTime() + @if (_order.PaymentDate != null) + { + تاریخ پرداخت: @_order.PaymentDate.ToDateTime().MiladiToJalaliWithTime() + } + else + { + تاریخ ثبت: @DateTime.Now.MiladiToJalaliWithTime() + } وضعیت: @GetStatusText(_order.PaymentStatus) - روش پرداخت: @GetPaymentMethodText(_order.PaymentMethod) آدرس: @_order.UserAddressText @@ -42,12 +48,12 @@ else - @context.ProductThumbnailPath + @context.ProductTitle - @FormatPrice(context.UnitPrice.Value) + @FormatPrice(context.UnitPrice ?? 0) @context.Count - @FormatPrice(context.UnitPrice.Value*context.Count.Value) + @FormatPrice((context.UnitPrice ?? 0) * (context.Count ?? 0)) @@ -64,10 +70,10 @@ else @it.ProductTitle - @FormatPrice(it.UnitPrice.Value) واحد + @FormatPrice(it.UnitPrice ?? 0) واحد تعداد: @it.Count - جمع: @FormatPrice(it.UnitPrice.Value*it.Count.Value) + جمع: @FormatPrice((it.UnitPrice ?? 0) * (it.Count ?? 0)) } @@ -77,7 +83,7 @@ else @* نمایش جزئیات مالی *@ @{ - var subtotal = _order.FactorDetails.Sum(s => s.UnitPrice.Value * s.Count.Value); + var subtotal = _order.FactorDetails.Sum(s => (s.UnitPrice ?? 0) * (s.Count ?? 0)); } diff --git a/src/FrontOffice.Main/Pages/Store/Orders.razor b/src/FrontOffice.Main/Pages/Store/Orders.razor index 1355e6d..a40cd5a 100644 --- a/src/FrontOffice.Main/Pages/Store/Orders.razor +++ b/src/FrontOffice.Main/Pages/Store/Orders.razor @@ -28,9 +28,18 @@ @context.Id - @context.PaymentDate.ToDateTime().MiladiToJalaliWithTime() + + @if (context.PaymentDate != null) + { + @context.PaymentDate.ToDateTime().MiladiToJalaliWithTime() + } + else + { + در انتظار پرداخت + } + @GetStatusText(context.PaymentStatus) - @FormatPrice(context.FactorDetails.Sum(s=>s.UnitPrice.Value*s.Count.Value)) + @FormatPrice(context.FactorDetails.Sum(s=>(s.UnitPrice ?? 0) * (s.Count ?? 0))) جزئیات @@ -46,11 +55,20 @@ سفارش #@o.Id - @o.PaymentDate.ToDateTime().MiladiToJalaliWithTime() + + @if (o.PaymentDate != null) + { + @o.PaymentDate.ToDateTime().MiladiToJalaliWithTime() + } + else + { + در انتظار پرداخت + } + وضعیت: @GetStatusText(o.PaymentStatus) - @FormatPrice(o.FactorDetails.Sum(s=>s.UnitPrice.Value*s.Count.Value)) + @FormatPrice(o.FactorDetails.Sum(s=>(s.UnitPrice ?? 0) * (s.Count ?? 0))) جزئیات diff --git a/src/FrontOffice.Main/Utilities/CartService.cs b/src/FrontOffice.Main/Utilities/CartService.cs index bbd8fcf..ad5c586 100644 --- a/src/FrontOffice.Main/Utilities/CartService.cs +++ b/src/FrontOffice.Main/Utilities/CartService.cs @@ -71,7 +71,7 @@ public class CartService { if (existing is null) { - await _client.AddNewUserCartAsync(new AddNewUserCartRequest + await _client.AddNewUserCartForCustomerAsync(new AddNewUserCartForCustomerRequest { ProductId = product.Id, Count = newQuantity @@ -80,10 +80,9 @@ public class CartService } else { - await _client.UpdateUserCartAsync(new UpdateUserCartRequest + await _client.UpdateUserCartForCustomerAsync(new UpdateUserCartForCustomerRequest { - UserCartId = existing.cartId, - + CartItemId = existing.cartId, Count = newQuantity }); } @@ -112,9 +111,9 @@ public class CartService try { - await _client.UpdateUserCartAsync(new UpdateUserCartRequest + await _client.UpdateUserCartForCustomerAsync(new UpdateUserCartForCustomerRequest { - UserCartId = existing.cartId, + CartItemId = existing.cartId, Count = quantity }); } @@ -134,11 +133,10 @@ public class CartService try { - // Interpret Count=0 as remove from cart - await _client.UpdateUserCartAsync(new UpdateUserCartRequest + // Use dedicated remove endpoint + await _client.RemoveUserCartForCustomerAsync(new RemoveUserCartForCustomerRequest { - UserCartId = cartId, - Count = 0 + CartItemId = cartId }); } catch @@ -158,12 +156,11 @@ public class CartService try { - foreach (var id in productIds) + foreach (var item in productIds) { - await _client.UpdateUserCartAsync(new UpdateUserCartRequest + await _client.RemoveUserCartForCustomerAsync(new RemoveUserCartForCustomerRequest { - UserCartId = id, - Count = 0 + CartItemId = item }); } } diff --git a/src/FrontOffice.Main/Utilities/ClubMembershipService.cs b/src/FrontOffice.Main/Utilities/ClubMembershipService.cs index 92ad728..9252a81 100644 --- a/src/FrontOffice.Main/Utilities/ClubMembershipService.cs +++ b/src/FrontOffice.Main/Utilities/ClubMembershipService.cs @@ -5,15 +5,19 @@ namespace FrontOffice.Main.Utilities; /// /// Service for Club Membership operations -/// Connected to FrontOffice.BFF gRPC ClubMembershipCQ +/// Connected to CMS gRPC ClubMembershipCQ /// public class ClubMembershipService { private readonly ClubMembershipContract.ClubMembershipContractClient _client; + private readonly UserAuthInfo _authInfo; - public ClubMembershipService(ClubMembershipContract.ClubMembershipContractClient client) + public ClubMembershipService( + ClubMembershipContract.ClubMembershipContractClient client, + UserAuthInfo authInfo) { _client = client; + _authInfo = authInfo; } /// @@ -24,8 +28,8 @@ public class ClubMembershipService { try { - var userId = await GetCurrentUserIdAsync(); - var response = await _client.GetClubMembershipAsync(new GetClubMembershipRequest { UserId = userId }); + // UserId = 0 میفرستیم تا CMS از JWT بخونه + var response = await _client.GetClubMembershipAsync(new GetClubMembershipRequest { UserId = 0 }); return new ClubMembershipDto { UserId = response.UserId, @@ -34,8 +38,11 @@ public class ClubMembershipService DaysRemaining = response.DaysRemaining > 0 ? response.DaysRemaining : null }; } - catch + catch (Exception ex) { + // Log the error for debugging + Console.WriteLine($"GetMyMembershipAsync error: {ex.Message}"); + // Return empty membership if backend is unavailable return new ClubMembershipDto { @@ -59,6 +66,19 @@ public class ClubMembershipService try { var userId = await GetCurrentUserIdAsync(); + + // Check if user is authenticated + if (userId <= 0) + { + return new ClubActivationResponseDto + { + Success = false, + Message = "کاربر احراز هویت نشده است. لطفاً ابتدا وارد شوید.", + ActivationDate = null, + AmountPaid = 0 + }; + } + var request = new ActivateClubMembershipRequest { UserId = userId, @@ -93,10 +113,8 @@ public class ClubMembershipService } } - private async Task GetCurrentUserIdAsync() + private Task GetCurrentUserIdAsync() { - // Get current user ID from auth service or JWT token - // This is a placeholder - should be implemented based on your auth system - return 1; // TODO: Implement proper user ID retrieval + return Task.FromResult(_authInfo.UserId); } } diff --git a/src/FrontOffice.Main/Utilities/WalletService.cs b/src/FrontOffice.Main/Utilities/WalletService.cs index 32b14a9..f1a0c30 100644 --- a/src/FrontOffice.Main/Utilities/WalletService.cs +++ b/src/FrontOffice.Main/Utilities/WalletService.cs @@ -6,7 +6,17 @@ using Google.Protobuf.WellKnownTypes; namespace FrontOffice.Main.Utilities; public record WalletBalances(long CreditBalance, long DiscountBalance, long NetworkBalance); -public record WalletTransaction(string Date, long Amount, string Channel, string Description); +public record WalletTransaction( + string Date, + long CreditChange, + long CreditBalance, + long NetworkChange, + long NetworkBalance, + long DiscountChange, + long DiscountBalance, + string Channel, + string Description +); public record WalletWithdrawal(long Id, long WeekDefinitionId, string WeekDisplayName, long Amount, int Status, int? Method, string? Iban, string Created); public enum WithdrawalMethodClient { @@ -52,12 +62,19 @@ public class WalletService request.IsIncrease = isIncrease.Value; } var response = await _client.GetCustomerWalletChangeLogAsync(request); + return response.Models - .Select(t => new WalletTransaction( - ResolveTransactionDate(t), - t.ChangeValue != 0 ? t.ChangeValue : t.CurrentBalance, - t.RefrenceId?.ToString() ?? "-", - ResolveDescription(t))) + .Select(log => new WalletTransaction( + ResolveTransactionDate(log), + log.ChangeValue, + log.CurrentBalance, + log.ChangeNerworkValue, + log.CurrentNetworkBalance, + log.ChangeDiscountValue, + log.CurrentDiscountBalance, + log.RefrenceId?.ToString() ?? "-", + ResolveDescription(log) + )) .OrderByDescending(t => t.Date) .ToList(); } @@ -74,7 +91,8 @@ public class WalletService { try { - return model.CreatedAt.ToDateTime().MiladiToJalaliWithTime(); + var utcDate = model.CreatedAt.ToDateTime(); + return utcDate.ToLocalTime().MiladiToJalaliWithTime(); } catch { @@ -87,9 +105,18 @@ public class WalletService private static string ResolveDescription(CustomerWalletChangeLogModel model) { - if (model.ChangeValue > 0) return "شارژ کیف پول"; - if (model.ChangeValue < 0) return "برداشت/خرید"; - return "تراکنش کیف پول"; + var parts = new List(); + + if (model.ChangeValue != 0) + parts.Add(model.ChangeValue > 0 ? "شارژ عادی" : "برداشت عادی"); + + if (model.ChangeNerworkValue != 0) + parts.Add(model.ChangeNerworkValue > 0 ? "دریافت شبکه" : "برداشت شبکه"); + + if (model.ChangeDiscountValue != 0) + parts.Add(model.ChangeDiscountValue > 0 ? "شارژ تخفیفی" : "خرید تخفیفی"); + + return parts.Count > 0 ? string.Join(" | ", parts) : "تراکنش کیف پول"; } public async Task RequestWithdrawalAsync(long payoutId, WithdrawalMethodClient method, string? iban) From 83d0ca1e0c79eb04eea720fe51e51f4732c6dcf6 Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Tue, 10 Feb 2026 22:06:13 +0330 Subject: [PATCH 14/23] fix: update Gateway URL and SignalR HubPath in staging configuration --- README.md | 1 - RELEASE-NOTES-v1.5.0.md | 38 ------------------- src/FrontOffice.Main/appsettings.Staging.json | 4 +- 3 files changed, 2 insertions(+), 41 deletions(-) delete mode 100644 README.md delete mode 100644 RELEASE-NOTES-v1.5.0.md diff --git a/README.md b/README.md deleted file mode 100644 index c3c41c3..0000000 --- a/README.md +++ /dev/null @@ -1 +0,0 @@ -# Test multi-remote push Sun Dec 7 19:09:00 UTC 2025 diff --git a/RELEASE-NOTES-v1.5.0.md b/RELEASE-NOTES-v1.5.0.md deleted file mode 100644 index 6cb9b02..0000000 --- a/RELEASE-NOTES-v1.5.0.md +++ /dev/null @@ -1,38 +0,0 @@ -# 🎉 به‌روزرسانی جدید - نسخه ۱.۵.۰ - -**تاریخ انتشار**: ۹ دی ۱۴۰۴ - ---- - -## ✨ امکانات جدید - -### 💰 بهبود صفحه پاداش‌ها -- **انتخابگر هفته هوشمند**: حالا می‌تونید با تایپ کردن، هفته مورد نظر رو سریع‌تر پیدا کنید -- **نمایش خلاصه**: در بالای صفحه، مجموع پاداش‌ها، مبلغ پرداخت شده و در انتظار رو ببینید -- **طراحی جدید موبایل**: تجربه بهتر در گوشی موبایل - -### 📊 جزئیات بیشتر در گزارش هفتگی -- **نمایش اعضای جدید**: تعداد اعضای جدید هر تیم در هفته -- **انتقال از هفته قبل**: مشاهده امتیازات منتقل شده از هفته گذشته - -### 🎨 بهبود رابط کاربری -- طراحی زیباتر کارت‌ها و جداول -- نمایش بهتر در تمام اندازه‌های صفحه نمایش - ---- - -## 🐛 رفع اشکال - -- رفع مشکل نمایش نادرست امتیازات منتقل شده -- بهبود سرعت بارگذاری صفحات - ---- - -## 💡 نکته - -برای دسترسی به پاداش‌های خود، از منوی **پروفایل** گزینه **پاداش‌های من** را انتخاب کنید. - ---- - -با تشکر از همراهی شما 🙏 -**تیم کارا بازار سلامت** diff --git a/src/FrontOffice.Main/appsettings.Staging.json b/src/FrontOffice.Main/appsettings.Staging.json index ff46774..4d768f1 100644 --- a/src/FrontOffice.Main/appsettings.Staging.json +++ b/src/FrontOffice.Main/appsettings.Staging.json @@ -1,12 +1,12 @@ { - "GwUrl": "https://frontoffice-bff.se.kbs1.ir", + "GwUrl": "https://cms.se.kbs1.ir", "DownloadUrl": "https://dl.afrino.co", "EncryptionSettings": { "Key": "kmcQ3XTmH4mrdh8VHziuscyf8LLYjG//Kyni81nH/0E=", "IV": "1wyF3Tt142MOkCpIyCxh/g==" }, "SignalR": { - "HubPath": "/hubs/token-relay" + "HubPath": "/hubs/token-notification" }, "Logging": { "LogLevel": { From f0d1156457b21145798f6798abca769085255c0d Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Wed, 11 Feb 2026 00:41:38 +0330 Subject: [PATCH 15/23] fix: update Docker daemon startup options and improve readiness checks in deployment workflows --- .gitea/workflows/kub-deploy.yml | 10 ++-- .gitea/workflows/prod-deploy.yml | 88 ++++++++++++++------------------ 2 files changed, 44 insertions(+), 54 deletions(-) diff --git a/.gitea/workflows/kub-deploy.yml b/.gitea/workflows/kub-deploy.yml index bc3d1ae..606a25a 100644 --- a/.gitea/workflows/kub-deploy.yml +++ b/.gitea/workflows/kub-deploy.yml @@ -26,7 +26,7 @@ jobs: } 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 for i in $(seq 1 90); do @@ -50,14 +50,18 @@ jobs: run: | 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 194.5.195.53:32082 -u admin --password-stdin + echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login ${{ env.REGISTRY }} -u admin --password-stdin + - name: Build Docker Image run: | cd src - 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 run: | - echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login ${{ env.REGISTRY }} -u admin --password-stdin docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest - name: Deploy to Kubernetes diff --git a/.gitea/workflows/prod-deploy.yml b/.gitea/workflows/prod-deploy.yml index fdcc1b0..5e7ada2 100644 --- a/.gitea/workflows/prod-deploy.yml +++ b/.gitea/workflows/prod-deploy.yml @@ -6,84 +6,70 @@ on: - production env: - REGISTRY: gitea-svc:3000 -<<<<<<< HEAD - EXTERNAL_REGISTRY: 194.5.195.53:30080 -======= - EXTERNAL_REGISTRY: git.kbs1.ir ->>>>>>> production + REGISTRY: 194.5.195.53:30080 IMAGE_NAME: admin/frontoffice + K8S_SERVER: 194.5.195.53 jobs: build-and-deploy: runs-on: ubuntu-latest container: - image: docker:latest + image: 194.5.195.53:32082/docker-sshpass:latest 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: - # - 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 run: | mkdir -p /etc/docker cat > /etc/docker/daemon.json << 'DAEMON' { -<<<<<<< HEAD - "insecure-registries": ["194.5.195.53:30080", "gitea-svc:3000"] -======= - "insecure-registries": ["194.5.195.53:32082", "git.kbs1.ir", "gitea-svc:3000"] ->>>>>>> production + "insecure-registries": ["194.5.195.53:30080", "194.5.195.53:32500", "194.5.195.53:32082"] } DAEMON - mkdir -p ~/.docker - cat > ~/.docker/config.json << 'CONF' - { - "proxies": { - "default": { - "httpProxy": "http://proxyuser:87zH26nbqT2@46.249.98.211:3128", - "httpsProxy": "http://proxyuser:87zH26nbqT2@46.249.98.211:3128", - "noProxy": "localhost,127.0.0.1,gitea-svc,194.5.195.53,10.0.0.0/8" - } - } - } - CONF - dockerd & - for i in $(seq 1 30); do docker info >/dev/null 2>&1 && break || sleep 2; done - + echo "🚀 Starting Docker daemon..." + dockerd --iptables=false --ip6tables=false --bridge=none --storage-driver=vfs & + + for i in $(seq 1 90); do + if docker info >/dev/null 2>&1; then + echo "✅ Docker daemon is ready (attempt $i)" + docker version + break + else + echo "⏳ Waiting for Docker daemon... (attempt $i/90)" + sleep 2 + fi + done + + if ! docker info >/dev/null 2>&1; then + echo "❌ Docker daemon failed to start after 3 minutes" + exit 1 + fi + - name: Checkout code run: | git clone --depth 1 --branch 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 194.5.195.53:32082 -u admin --password-stdin + echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login ${{ env.REGISTRY }} -u admin --password-stdin - name: Build Docker Image run: | cd src - docker build -f FrontOffice.Main/Dockerfile \ - -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} \ + DOCKER_BUILDKIT=0 docker build --network host -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} \ -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:prod \ - -t ${{ env.EXTERNAL_REGISTRY }}/${{ env.IMAGE_NAME }}:prod \ - . + -f FrontOffice.Main/Dockerfile . - name: Push to Registry 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 }}: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 run: | - mkdir -p ~/.kube - echo "${{ secrets.KUBECONFIG_PROD }}" | base64 -d > ~/.kube/config - kubectl rollout restart deployment/frontoffice || echo "Deployment not found" - kubectl rollout status deployment/frontoffice --timeout=5m || echo "Rollout pending" + export SSHPASS="${{ secrets.SERVER_PASSWORD }}" + sshpass -e ssh -o StrictHostKeyChecking=no root@${{ env.K8S_SERVER }} " + kubectl rollout restart deployment/frontoffice + kubectl rollout status deployment/frontoffice --timeout=300s + " + echo "✅ Deployed to Production!" From 6db83ccd90aca7080dafcc450f927b46b7561aa2 Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Mon, 16 Feb 2026 00:51:39 +0330 Subject: [PATCH 16/23] feat: full FrontOffice updates - discount store, blog, payment gateway, UI improvements - Discount store pages (products, cart, orders, order detail) - Blog pages and services - Payment gateway callback page - AppImage component, EmptyState, LoadingState, PageHeader - DiscountCartService, DiscountOrderService, DiscountProductService - BlogCategoryService, BlogPostService, SitePageService, ImageCacheService - PhoneVerifyForm component - Profile Hub page - UI/UX improvements across all pages - landing.js for homepage - Config and routing updates --- docs/CHANGELOG.md | 240 ++++ docs/UI-UNIFICATION-PLAN.md | 542 ++++++++ src/FrontOffice.Main/ConfigureServices.cs | 28 + src/FrontOffice.Main/Pages/About.razor | 434 +++--- src/FrontOffice.Main/Pages/About.razor.cs | 33 +- src/FrontOffice.Main/Pages/Blog/Index.razor | 172 +++ .../Pages/Blog/Index.razor.cs | 91 ++ src/FrontOffice.Main/Pages/Blog/Post.razor | 122 ++ src/FrontOffice.Main/Pages/Blog/Post.razor.cs | 51 + src/FrontOffice.Main/Pages/Checkout.razor | 15 +- src/FrontOffice.Main/Pages/Checkout.razor.cs | 2 +- .../Pages/Club/FeaturesPage.razor | 18 +- .../Pages/Club/MembershipPage.razor | 7 +- .../Commission/CommissionDashboardPage.razor | 7 +- .../Pages/Commission/WeeklyBalancePage.razor | 17 +- src/FrontOffice.Main/Pages/Contact.razor | 46 +- src/FrontOffice.Main/Pages/Contact.razor.cs | 79 +- .../Pages/DiscountStore/Cart.razor | 190 +++ .../Pages/DiscountStore/Cart.razor.cs | 56 + .../Pages/DiscountStore/Checkout.razor | 193 +++ .../Pages/DiscountStore/Checkout.razor.cs | 146 ++ .../Pages/DiscountStore/OrderDetail.razor | 198 +++ .../Pages/DiscountStore/OrderDetail.razor.cs | 76 ++ .../Pages/DiscountStore/Orders.razor | 130 ++ .../Pages/DiscountStore/Orders.razor.cs | 65 + .../Pages/DiscountStore/ProductDetail.razor | 140 ++ .../DiscountStore/ProductDetail.razor.cs | 64 + .../Pages/DiscountStore/Products.razor | 133 ++ .../Pages/DiscountStore/Products.razor.cs | 87 ++ src/FrontOffice.Main/Pages/FAQ.razor | 2 +- .../Pages/Gateway/CartChooser.razor | 62 + .../Pages/Gateway/CartChooser.razor.cs | 47 + .../Pages/Gateway/OrdersChooser.razor | 49 + .../Pages/Gateway/StoreChooser.razor | 56 + .../Pages/Gateway/StoreChooser.razor.cs | 43 + src/FrontOffice.Main/Pages/Index.razor | 647 +++++---- src/FrontOffice.Main/Pages/Index.razor.cs | 188 +-- .../Pages/Network/NetworkStatisticsPage.razor | 7 +- .../Pages/Package/MyPackages.razor | 11 +- .../Pages/Package/Packages.razor | 8 +- .../Pages/PackageDetail.razor | 31 +- .../Pages/PackageDetail.razor.cs | 2 +- .../Pages/Profile/Addresses.razor | 5 +- .../Pages/Profile/ChangePassword.razor | 5 +- .../Components/OrganizationChart.razor | 12 +- .../Components/OrganizationChartLevel.razor | 6 +- src/FrontOffice.Main/Pages/Profile/Hub.razor | 64 + .../Pages/Profile/Hub.razor.cs | 34 + .../Pages/Profile/Index.razor | 428 +++--- .../Pages/Profile/Index.razor.cs | 60 +- .../Pages/Profile/PaymentCallback.razor | 7 +- .../Pages/Profile/Personal.razor | 7 +- .../Pages/Profile/Personal.razor.cs | 19 +- .../Pages/Profile/Settings.razor | 5 +- .../Pages/Profile/Settings.razor.cs | 15 +- src/FrontOffice.Main/Pages/Profile/Tree.razor | 5 +- .../Pages/Profile/Wallet.razor | 5 +- .../Pages/Profile/WithdrawalRequests.razor | 7 +- .../Pages/RegisterWizard.razor | 123 +- .../Pages/RegisterWizard.razor.cs | 10 +- src/FrontOffice.Main/Pages/Store/Cart.razor | 18 +- .../Pages/Store/Cart.razor.cs | 2 +- .../Pages/Store/Categories.razor | 2 +- .../Pages/Store/CheckoutSummary.razor | 11 +- .../Pages/Store/CheckoutSummary.razor.cs | 2 +- .../Pages/Store/OrderDetail.razor | 28 +- .../Pages/Store/OrderDetail.razor.cs | 2 +- .../Pages/Store/OrderTracking.razor | 3 +- .../Pages/Store/OrderTracking.razor.cs | 4 + src/FrontOffice.Main/Pages/Store/Orders.razor | 6 +- .../Pages/Store/ProductDetail.razor | 19 +- .../Pages/Store/Products.razor | 21 +- src/FrontOffice.Main/Pages/_Host.cshtml | 11 +- src/FrontOffice.Main/Shared/AppImage.razor | 101 ++ src/FrontOffice.Main/Shared/AuthDialog.razor | 249 ++-- .../Shared/AuthDialog.razor.cs | 21 +- .../Shared/ClubMembershipContractDialog.razor | 5 +- src/FrontOffice.Main/Shared/EmptyState.razor | 67 + .../Shared/LoadingState.razor | 18 + src/FrontOffice.Main/Shared/MainLayout.razor | 218 ++- .../Shared/MainLayout.razor.cs | 117 +- src/FrontOffice.Main/Shared/PageHeader.razor | 40 + .../Shared/PhoneVerifyForm.razor | 96 ++ .../Shared/PhoneVerifyForm.razor.cs | 42 + .../Utilities/AuthDialogService.cs | 14 +- src/FrontOffice.Main/Utilities/AuthService.cs | 2 +- .../Utilities/BlogCategoryService.cs | 60 + .../Utilities/BlogPostService.cs | 204 +++ src/FrontOffice.Main/Utilities/CartService.cs | 2 +- .../Utilities/CustomMudTheme.cs | 145 +- .../Utilities/DiscountCartService.cs | 151 ++ .../Utilities/DiscountOrderService.cs | 209 +++ .../Utilities/DiscountProductService.cs | 207 +++ .../Utilities/ImageCacheService.cs | 122 ++ .../Utilities/OrderService.cs | 4 +- .../Utilities/PackageService.cs | 4 +- .../Utilities/ProductService.cs | 4 +- .../Utilities/RouteConstants.cs | 27 + .../Utilities/SitePageService.cs | 114 ++ src/FrontOffice.Main/Utilities/UrlUtility.cs | 12 + src/FrontOffice.Main/appsettings.Staging.json | 2 +- src/FrontOffice.Main/appsettings.json | 2 +- src/FrontOffice.Main/wwwroot/css/site.css | 1211 ++++++++++++++++- src/FrontOffice.Main/wwwroot/js/landing.js | 87 ++ 104 files changed, 7668 insertions(+), 1370 deletions(-) create mode 100644 docs/CHANGELOG.md create mode 100644 docs/UI-UNIFICATION-PLAN.md create mode 100644 src/FrontOffice.Main/Pages/Blog/Index.razor create mode 100644 src/FrontOffice.Main/Pages/Blog/Index.razor.cs create mode 100644 src/FrontOffice.Main/Pages/Blog/Post.razor create mode 100644 src/FrontOffice.Main/Pages/Blog/Post.razor.cs create mode 100644 src/FrontOffice.Main/Pages/DiscountStore/Cart.razor create mode 100644 src/FrontOffice.Main/Pages/DiscountStore/Cart.razor.cs create mode 100644 src/FrontOffice.Main/Pages/DiscountStore/Checkout.razor create mode 100644 src/FrontOffice.Main/Pages/DiscountStore/Checkout.razor.cs create mode 100644 src/FrontOffice.Main/Pages/DiscountStore/OrderDetail.razor create mode 100644 src/FrontOffice.Main/Pages/DiscountStore/OrderDetail.razor.cs create mode 100644 src/FrontOffice.Main/Pages/DiscountStore/Orders.razor create mode 100644 src/FrontOffice.Main/Pages/DiscountStore/Orders.razor.cs create mode 100644 src/FrontOffice.Main/Pages/DiscountStore/ProductDetail.razor create mode 100644 src/FrontOffice.Main/Pages/DiscountStore/ProductDetail.razor.cs create mode 100644 src/FrontOffice.Main/Pages/DiscountStore/Products.razor create mode 100644 src/FrontOffice.Main/Pages/DiscountStore/Products.razor.cs create mode 100644 src/FrontOffice.Main/Pages/Gateway/CartChooser.razor create mode 100644 src/FrontOffice.Main/Pages/Gateway/CartChooser.razor.cs create mode 100644 src/FrontOffice.Main/Pages/Gateway/OrdersChooser.razor create mode 100644 src/FrontOffice.Main/Pages/Gateway/StoreChooser.razor create mode 100644 src/FrontOffice.Main/Pages/Gateway/StoreChooser.razor.cs create mode 100644 src/FrontOffice.Main/Pages/Profile/Hub.razor create mode 100644 src/FrontOffice.Main/Pages/Profile/Hub.razor.cs create mode 100644 src/FrontOffice.Main/Shared/AppImage.razor create mode 100644 src/FrontOffice.Main/Shared/EmptyState.razor create mode 100644 src/FrontOffice.Main/Shared/LoadingState.razor create mode 100644 src/FrontOffice.Main/Shared/PageHeader.razor create mode 100644 src/FrontOffice.Main/Shared/PhoneVerifyForm.razor create mode 100644 src/FrontOffice.Main/Shared/PhoneVerifyForm.razor.cs create mode 100644 src/FrontOffice.Main/Utilities/BlogCategoryService.cs create mode 100644 src/FrontOffice.Main/Utilities/BlogPostService.cs create mode 100644 src/FrontOffice.Main/Utilities/DiscountCartService.cs create mode 100644 src/FrontOffice.Main/Utilities/DiscountOrderService.cs create mode 100644 src/FrontOffice.Main/Utilities/DiscountProductService.cs create mode 100644 src/FrontOffice.Main/Utilities/ImageCacheService.cs create mode 100644 src/FrontOffice.Main/Utilities/SitePageService.cs create mode 100644 src/FrontOffice.Main/wwwroot/js/landing.js diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md new file mode 100644 index 0000000..a4af557 --- /dev/null +++ b/docs/CHANGELOG.md @@ -0,0 +1,240 @@ +# 📋 تاریخچه تغییرات FrontOffice — کارا بازار سلامت + +> **آخرین بروزرسانی**: بهمن ۱۴۰۴ (فوریه ۲۰۲۶) +> **فریمورک**: Blazor Server + MudBlazor 8.14 + .NET 9 + +--- + +## 🔖 نسخه ۲.۵.۰ — بهمن ۱۴۰۴ + +### ۱. محافظت صفحات نیازمند احراز هویت (Auth Guard) + +**فایل‌های تغییریافته:** +- `Shared/MainLayout.razor.cs` + +**شرح:** +قبلاً هیچ محافظتی در سطح مسیریابی وجود نداشت — کاربر غیرلاگین می‌توانست مستقیماً به `/profile/*`، `/commission/*`، `/cart` و... دسترسی پیدا کند. + +**تغییرات:** +- متد `EnforceAuthGuardAsync()` اضافه شد — در هر تغییر مسیر و اولین بار رندر اجرا می‌شود +- متد `IsProtectedRoute(path)` مسیرهای محافظت‌شده را تشخیص می‌دهد +- کاربر غیرلاگین → ریدایرکت به `/` (صفحه اصلی) + +**مسیرهای محافظت‌شده:** +| گروه | مسیرها | +|---|---| +| پروفایل | `/profile/*` | +| کمیسیون | `/commission/*` | +| شبکه | `/network/*` | +| باشگاه | `/club/*` | +| سبد خرید | `/cart`, `/checkout*`, `/orders`, `/order/*`, `/order-tracking/*` | +| پکیج‌ها | `/my-packages` | +| دروازه | `/my-orders`, `/my-cart` | +| فروشگاه تخفیفی | `/discount-store/cart`, `/discount-store/checkout`, `/discount-store/orders`, `/discount-store/order/*` | + +**مسیرهای عمومی:** +`/`, `/register`, `/about`, `/faq`, `/contact`, `/blog/*`, `/packages`, `/package/*`, `/products`, `/product/*`, `/categories`, `/stores`, `/discount-store`, `/discount-store/product/*` + +--- + +### ۲. استخراج کامپوننت PhoneVerifyForm + +**فایل‌های جدید:** +- `Shared/PhoneVerifyForm.razor` +- `Shared/PhoneVerifyForm.razor.cs` + +**فایل‌های تغییریافته:** +- `Shared/AuthDialog.razor` +- `Shared/AuthDialog.razor.cs` + +**شرح:** +فرم تلفن + تایید OTP + کپچا که قبلاً به‌صورت `RenderFragment` با `__builder` مستقیم در `AuthDialog` نوشته شده بود، به کامپوننت مستقل `PhoneVerifyForm` استخراج شد. + +**ساختار قبل:** +``` +AuthDialog.razor + └── @code { PhoneOrVerifyContent() => __builder => { ... } } ← RenderFragment پیچیده +``` + +**ساختار بعد:** +``` +AuthDialog.razor + └── ← کامپوننت مستقل +PhoneVerifyForm.razor ← مارکاپ فرم +PhoneVerifyForm.razor.cs ← پارامترها و فرم رف‌ها +``` + +**پارامترهای PhoneVerifyForm:** + +| پارامتر | نوع | توضیح | +|---|---|---| +| `CurrentStep` | `AuthStep` | مرحله فعلی (Phone / Verify) | +| `PhoneRequest` | `CreateNewOtpTokenRequest` | مدل فرم تلفن | +| `VerifyRequest` | `VerifyOtpTokenRequest` | مدل فرم تایید | +| `CaptchaCode` | `string?` | کد کپچا نمایش‌داده‌شده | +| `CaptchaInput` / `CaptchaInputChanged` | `string?` + `EventCallback` | ورودی کپچا (two-way) | +| `OnRefreshCaptcha` | `EventCallback` | رفرش کپچا | +| `IsBusy` | `bool` | وضعیت بارگذاری | +| `ErrorMessage` / `InfoMessage` | `string?` | پیام‌های خطا/اطلاع | +| `PhoneNumber` | `string?` | شماره تایید‌شده | +| `ResendRemaining` | `int` | ثانیه تا ارسال مجدد | +| `OnChangePhone` / `OnResendOtp` | `EventCallback` | اکشن‌های تایید | + +**نکته مهم — Two-way binding کپچا:** +فیلد کپچا با `Value` + `ValueChanged` بایند شده (نه `@bind-Value`) تا مقدار تایپ‌شده از فرزند به والد برگردد: +```razor + +``` + +**دسترسی به فرم‌ها از والد:** +```csharp +// AuthDialog.razor.cs +var phoneForm = _phoneVerifyForm?.GetPhoneForm(); +var verifyForm = _phoneVerifyForm?.GetVerifyForm(); +``` + +--- + +### ۳. بهبود لایوت مدال ورود (AuthDialog) + +**فایل‌های تغییریافته:** +- `Shared/AuthDialog.razor` +- `Utilities/AuthDialogService.cs` +- `wwwroot/css/site.css` + +**مشکلات قبلی:** +- دیالوگ روی موبایل `FullScreen` بود → فضای خالی بزرگ بین فرم و دکمه‌ها +- `TitleContent` و `DialogActions` جدا → گپ عمودی +- ردیف کپچا با `MudStack Row="true"` → آیتم‌ها عمودی رندر می‌شدند + +**تغییرات:** +1. **حذف FullScreen**: `AuthDialogService` حالا همیشه `MaxWidth.ExtraSmall, FullWidth=true, CloseButton=true` +2. **ادغام محتوا**: آواتار + عنوان + فرم + دکمه‌ها همه داخل `DialogContent` → بدون `TitleContent` و `DialogActions` +3. **کلاس `auth-dialog-wrapper`**: CSS با `.auth-dialog-wrapper .mud-dialog-title { display: none; }` عنوان پیش‌فرض دیالوگ رو مخفی می‌کنه +4. **ردیف کپچا**: از `MudStack Row` به `div.captcha-row` با CSS flex اختصاصی +5. **حالت Inline**: بدون تغییر ساختاری — فقط از `PhoneVerifyForm` استفاده می‌کنه + +**CSS جدید:** +```css +.auth-dialog-wrapper .mud-dialog-title { display: none; } +.auth-dialog-wrapper .mud-dialog-content { padding-bottom: 24px !important; } +.auth-dialog-content { max-width: 400px; margin: 0 auto; } + +.captcha-row { display: flex; align-items: center; gap: 10px; } +.captcha-row > .mud-input-control { flex: 1 1 0; min-width: 0; } +.captcha-row > .mud-paper { flex: 0 0 auto; } +.captcha-row > .mud-button-root { flex: 0 0 auto; } +``` + +--- + +### ۴. صفحه لندینگ — حذف /pricing و اضافه بنر بلاگ + +**فایل‌های تغییریافته:** +- `Pages/Index.razor` +- `wwwroot/css/site.css` + +**تغییرات:** +1. دکمه هیرو «مشاهده پکیج‌ها» → **«آخرین اخبار»** با لینک `/blog` +2. **بنر جدیدترین مطلب** بین هیرو و «سه گام تا شروع» اضافه شد + - تصویر بندانگشتی + عنوان + خلاصه + آیکون شیشه‌ای + - هاور: `translateY(-2px)` + سایه بزرگ‌تر + +**CSS جدید:** +```css +.landing-blog-banner { border: 1px solid var(--mud-palette-divider); transition: ... } +.landing-blog-banner:hover { box-shadow: var(--mud-elevation-4); transform: translateY(-2px); } +.landing-blog-thumb { width: 80px; height: 80px; border-radius: 12px; } +.landing-blog-title { -webkit-line-clamp: 1; font-weight: 600; } +.landing-blog-summary { -webkit-line-clamp: 1; } +``` + +--- + +### ۵. افزایش ارتفاع تکست‌باکس‌ها (Global) + +**فایل تغییریافته:** +- `wwwroot/css/site.css` + +**قبل:** `padding: 10px 14px` +**بعد:** `padding: 14px 14px` + `font-size: 1rem` + +```css +.mud-input-outlined .mud-input-slot { + padding: 14px 14px !important; + font-size: 1rem; +} +``` + +تمام فیلدهای Outlined در کل اپلیکیشن بزرگ‌تر شدند. + +--- + +### ۶. اصلاح RTL فیلدهای ورودی + +**فایل تغییریافته:** +- `wwwroot/css/site.css` + +**مشکل:** فیلدهای `type="tel"` به‌صورت پیش‌فرض مرورگر `direction: ltr` می‌گیرن — لیبل سمت راست ولی placeholder/cursor سمت چپ. + +**اصلاح:** +```css +.mud-input-slot input, +.mud-input-slot textarea { + direction: rtl !important; + text-align: right !important; +} +``` + +--- + +### ۷. اصلاح captcha-box CSS + +**فایل تغییریافته:** +- `wwwroot/css/site.css` + +**قبل:** +```css +.captcha-box { + min-width: 120px; min-height: 56px; + background: linear-gradient(135deg, rgba(123,97,255,.12), rgba(255,140,189,.12)); +} +``` + +**بعد:** +```css +.captcha-box { + min-width: 96px; min-height: 48px; + border-radius: var(--mud-default-borderradius); + background: rgba(99,102,241,.08); + border: 1px solid var(--mud-palette-divider); +} +``` + +--- + +## 📁 نقشه فایل‌ها + +``` +Shared/ +├── AuthDialog.razor ← بازنویسی (کامپوننت PhoneVerifyForm جایگزین RenderFragment) +├── AuthDialog.razor.cs ← بروزرسانی (استفاده از _phoneVerifyForm) +├── PhoneVerifyForm.razor ← ✨ جدید (فرم تلفن + تایید + کپچا) +├── PhoneVerifyForm.razor.cs ← ✨ جدید (پارامترها و فرم رف‌ها) +├── MainLayout.razor.cs ← Auth Guard اضافه شد +Pages/ +├── Index.razor ← /pricing → /blog + بنر بلاگ +Utilities/ +├── AuthDialogService.cs ← حذف FullScreen، ثابت‌سازی سایز +wwwroot/css/ +├── site.css ← captcha-row, auth-dialog, RTL fix, input height +``` + +--- + +## 🔍 وضعیت بیلد + +| تاریخ | خطا | هشدار | توضیح | +|---|---|---|---| +| بهمن ۱۴۰۴ | **۰** | ۱۰۵ | MUD0002 warnings (بی‌خطر — مربوط به MudBlazor analyzer) | diff --git a/docs/UI-UNIFICATION-PLAN.md b/docs/UI-UNIFICATION-PLAN.md new file mode 100644 index 0000000..daa7d19 --- /dev/null +++ b/docs/UI-UNIFICATION-PLAN.md @@ -0,0 +1,542 @@ +# 🎨 پلن جامع یکپارچه‌سازی UI/UX — کارا بازار سلامت + +> **تاریخ**: بهمن ۱۴۰۴ +> **وضعیت**: ✅ **تمام ۶ فاز + فاز ۷ (ناوبری و امنیت) تکمیل شد** +> **هدف**: یکپارچه‌سازی طراحی FrontOffice با ۲۰٪ تغییر UX و ۵۰٪ تغییر UI +> **فریمورک**: Blazor Server + MudBlazor 8.14 +> **📋 تاریخچه تغییرات جزئی**: [CHANGELOG.md](CHANGELOG.md) + +--- + +## 📊 خلاصه اجرایی + +| شاخص | وضعیت قبل | وضعیت بعد | +|---|---|---| +| **صفحات کل** | ۴۱ صفحه + ۶ کامپوننت مشترک | ۴۱ صفحه + ۸ کامپوننت مشترک | +| **الگوی PageHeader** | ۱۹ صفحه از ۴۱ | ۳۲+ صفحه ✅ | +| **Inline Style سنگین** | ۱۱ صفحه 🔴 | ≤۲ صفحه (فقط gradient‌های تزئینی) ✅ | +| **Loading State** | ۲ الگوی متفاوت (Circular vs Linear) | `` واحد ✅ | +| **Empty State** | ۴+ الگوی متناقض | `` واحد ✅ | +| **Container Spacing** | ۶ الگوی متناقض | `py-6` استاندارد ✅ | +| **رنگ‌بندی** | رنگ‌های hardcoded در ۸+ صفحه | CSS Variable ✅ | +| **تم پالت** | Primary `#0380C0` (آبی ساده) | Indigo `#6366f1` + Full PaletteDark ✅ | +| **Elevation** | مخلوط ۲/۳/۴ | استاندارد ۰–۲ ✅ | +| **Build** | ۰ خطا | ۰ خطا ✅ | + +### فازها +| فاز | عنوان | وضعیت | +|---|---|---| +| ۱ | زیرساخت دیزاین سیستم | ✅ تکمیل | +| ۲ | صفحات پروفایل | ✅ تکمیل | +| ۳ | صفحات تخصصی | ✅ تکمیل | +| ۴ | بهبود بصری فروشگاه | ✅ تکمیل | +| ۵ | صفحات عمومی | ✅ تکمیل | +| ۶ | پالیش و تست | ✅ تکمیل | +| ۷ | ناوبری، امنیت و بازسازی کامپوننت‌ها | ✅ تکمیل | + +--- + +## 🔍 بخش ۱: تحلیل ضعف‌های جاری + +### ۱.۱ ناسازگاری‌های ساختاری (Structural) + +#### ❌ ۱.۱.۱ — PageHeader دوگانه +**مشکل**: نیمی از صفحات از `` استفاده می‌کنند، نیم دیگر header دستی دارند. + +| از `` استفاده می‌کنند ✅ | Header دستی دارند ❌ | +|---|---| +| Store/* (۸ صفحه) | Profile/* (۷ صفحه) | +| DiscountStore/* (۶ صفحه) | Club/* (۲ صفحه) | +| Gateway/* (۳ صفحه) | Commission/* (۲ صفحه) | +| Package/* (۲ صفحه) | Network/* (۱ صفحه) | +| PackageDetail, Checkout | Blog/*, Index, About, Contact, FAQ | + +**تأثیر**: ظاهر متفاوت دکمه بازگشت، فاصله‌بندی ناهماهنگ + +#### ❌ ۱.۱.۲ — Container MaxWidth متناقض +``` +MaxWidth.Large → اکثر صفحات +MaxWidth.Medium → Personal, Settings, OrderTracking, Blog/Post, Gateway/* +MaxWidth.Small → ChangePassword +ترکیبی (loading≠content) → OrderDetail, ProductDetail, PackageDetail +``` + +**تأثیر**: برخی صفحات پهن‌تر از حد نیاز هستند (مثلاً فرم‌های ساده با Large) + +#### ❌ ۱.۱.۳ — Container Padding متناقض +``` +py-6 → اکثریت (استاندارد) +py-8 → Package/Packages, Package/MyPackages +pa-2 pa-md-6 → Store/Products, DiscountStore/Products +py-6 py-md-10 → Gateway/* +py-4 py-md-6 → DiscountStore/ProductDetail +py-16 سکشنی → Index, About, Contact, FAQ +``` + +**قاعده پیشنهادی**: `py-6` برای صفحات داخلی، section-based برای صفحات عمومی + +--- + +### ۱.۲ ناسازگاری‌های بصری (Visual) + +#### ❌ ۱.۲.۱ — Inline Style سنگین (۱۱ صفحه) + +| صفحه | نمونه مشکل‌دار | +|---|---| +| **Index.razor** | `Style="color:#fff; font-size:clamp(1.6rem,4.5vw,2.4rem);"` | +| **Profile/Index** | `Style="background:rgba(99,102,241,.12);"` | +| **Store/ProductDetail** | `style="width:100%;height:360px;background-image:url(...);"` | +| **PackageDetail** | `Style="background: radial-gradient(600px 280px..."` | +| **Checkout** | `Style="background: radial-gradient(..."` + `Elevation="4"` | +| **Blog/Index** | `Style="color:#fff; font-weight:700;"`, `Style="font-size:3.5rem;"` | +| **Blog/Post** | `Style="width:100%; height:100%;"`, `Style="font-size:4rem;"` | +| **About** | `Style="background: radial-gradient(...);"` | +| **WeeklyBalance** | `Style="background: linear-gradient(135deg, #e8f5e9..."` | +| **Gateway/**** | `Style="background:rgba(16,185,129,.12)..."`, `Style="color:#10b981;"` | +| **DiscountStore/ProductDetail** | `Style="background:rgba(16,185,129,.06)..."` | + +#### ❌ ۱.۲.۲ — رنگ‌های Hardcoded + +| رنگ | استفاده | باید باشد | +|---|---|---| +| `#10b981` | Gateway (سبز فروشگاه) | `var(--ds-color-store)` | +| `#ef4444` | Gateway (قرمز تخفیفی) | `var(--ds-color-discount)` | +| `#6366f1`, `#818cf8`, `#a78bfa` | Hero/Banner gradients | `var(--ds-gradient-primary)` | +| `rgba(99,102,241,.12)` | Dashboard backgrounds | `var(--ds-primary-soft)` | +| `rgba(16,185,129,.06)` | Discount product highlights | `var(--ds-success-soft)` | +| `#e8f5e9`, `#c8e6c9` | WeeklyBalance stat cards | `var(--ds-success-gradient)` | + +#### ❌ ۱.۲.۳ — Elevation ناهماهنگ +``` +Elevation="0" → Blog cards (با border) +Elevation="1" → برخی صفحات +Elevation="2" → اکثر صفحات (استاندارد) +Elevation="3" → WeeklyBalance stat cards +Elevation="4" → Checkout.razor +``` + +**قاعده پیشنهادی**: `Elevation="0"` با `border` = کارت‌های اطلاعاتی، `Elevation="2"` = default + +#### ❌ ۱.۲.۴ — Paper Rounding ناهماهنگ +``` +rounded-lg (16px) → اکثریت +rounded-xl (20px) → Index, Profile/Index, Gateway, DiscountStore/ProductDetail +بدون class → design system !important → 12px +``` + +--- + +### ۱.۳ ناسازگاری‌های UX (تجربه کاربری) + +#### ❌ ۱.۳.۱ — Loading State دوگانه +``` +MudProgressCircular → Store/*, DiscountStore/*, Package/*, About, Blog, Addresses +MudProgressLinear → Club/*, Commission/*, Network/*, RegisterWizard +``` + +**مشکل**: کاربر دو تجربه مختلف «در حال بارگذاری» می‌بیند + +#### ❌ ۱.۳.۲ — Empty State ناهماهنگ +``` +MudAlert Severity.Info → Store/Orders, Categories, DiscountStore/Orders +Icon + Text + Button → Addresses, Package/MyPackages, PackageDetail +MudAlert Severity.Warning → WithdrawalRequests +Custom dashed-border paper → Blog/Index +``` + +#### ❌ ۱.۳.۳ — Routing Directive ناهماهنگ +``` +@attribute [Route(RouteConstants...)] → ۳۹ صفحه ✅ +@page "/categories" → Categories.razor ❌ +@page "/blog/{Slug}" → Blog/Post.razor ❌ +``` + +#### ❌ ۱.۳.۴ — PackageDetail Loading/Error بدون Container +Loading و Error state در `PackageDetail.razor` بدون `MudContainer` رندر می‌شوند → محتوا تمام‌عرض نمایش می‌یابد. + +#### ❌ ۱.۳.۵ — تم فعلی کم‌رنگ +```csharp +// CustomMudTheme.cs فعلی +Primary = "#0380C0" // آبی ساده +// بدون Secondary، Tertiary، Info، Warning تعریف‌شده +// بدون PaletteDark +// بدون LayoutProperties +``` + +**مشکلات**: +- فقط Primary تعریف شده، بقیه رنگ‌ها default MudBlazor +- Dark mode بدون palette اختصاصی +- بدون `DefaultBorderRadius`، `AppbarHeight` و غیره +- تناقض بین Primary `#0380C0` و gradient‌های CSS با `#6366f1` + +--- + +## 🎯 بخش ۲: معماری دیزاین سیستم هدف + +### ۲.۱ سلسله‌مراتب صفحات + +``` +┌─────────────────────────────────────────────┐ +│ MainLayout │ +│ ├─ AppBar (fixed, transparent) │ +│ ├─ MudMainContent │ +│ │ ├─ [Public Pages] → Section-based │ +│ │ │ (Index, About, Contact, FAQ, Blog) │ +│ │ └─ [Internal Pages] → Container-based │ +│ │ ├─ │ +│ │ ├─ Content (MudStack/MudGrid) │ +│ │ └─ │ +│ ├─ Footer (hidden on mobile) │ +│ └─ BottomNav (mobile only) │ +└─────────────────────────────────────────────┘ +``` + +### ۲.۲ قواعد واحد (Single Source of Truth) + +| قاعده | مقدار | +|---|---| +| **Container MaxWidth** | `Large` = لیست/گرید, `Medium` = فرم/جزئیات, `Small` = تک‌فرم ساده | +| **Container Spacing** | `py-6` صفحات داخلی, section-based صفحات عمومی | +| **Paper Elevation** | `0` با border = کارت اطلاعاتی, `2` = default | +| **Paper Rounding** | `rounded-lg` = default, `rounded-xl` = hero/banner | +| **Loading State** | `` component واحد | +| **Empty State** | `` component واحد | +| **Page Header** | `` در تمام صفحات داخلی | +| **Content Wrapper** | `MudStack Spacing="3"` بعد از PageHeader | + +### ۲.۳ CSS Variables هدف + +```css +:root { + /* ── Brand Colors ── */ + --ds-brand-primary: #6366f1; /* Indigo — هویت اصلی */ + --ds-brand-secondary: #8b5cf6; /* Purple */ + --ds-brand-accent: #a78bfa; /* Light purple */ + + /* ── Semantic Colors ── */ + --ds-color-store: #10b981; /* فروشگاه عادی */ + --ds-color-discount: #ef4444; /* فروشگاه تخفیفی */ + --ds-color-success: #10b981; + --ds-color-warning: #f59e0b; + --ds-color-error: #ef4444; + --ds-color-info: #3b82f6; + + /* ── Soft Backgrounds ── */ + --ds-primary-soft: rgba(99,102,241,.08); + --ds-success-soft: rgba(16,185,129,.08); + --ds-error-soft: rgba(239,68,68,.08); + --ds-warning-soft: rgba(245,158,11,.08); + + /* ── Gradients ── */ + --ds-gradient-primary: linear-gradient(135deg, #6366f1 0%, #818cf8 50%, #a78bfa 100%); + --ds-gradient-store: linear-gradient(135deg, #10b981 0%, #34d399 100%); + --ds-gradient-discount: linear-gradient(135deg, #ef4444 0%, #f97316 50%, #f59e0b 100%); + --ds-gradient-success: linear-gradient(135deg, #d1fae5 0%, #a7f3d0 100%); + + /* ── Spacing (existing) ── */ + --ds-radius-sm: 8px; + --ds-radius-md: 12px; + --ds-radius-lg: 16px; + --ds-radius-xl: 20px; + --ds-transition: 0.2s ease; + --ds-shadow-sm: 0 1px 3px rgba(0,0,0,.06); + --ds-shadow-md: 0 4px 12px rgba(0,0,0,.08); + --ds-shadow-lg: 0 8px 24px rgba(0,0,0,.10); +} +``` + +--- + +## 🚀 بخش ۳: فازبندی اجرا + +### 🔷 فاز ۱ — زیرساخت دیزاین سیستم (UI ~15%) +> **اولویت**: بالا | **ریسک**: پایین | **حجم**: ۶ فایل + +| # | تسک | فایل | نوع تغییر | +|---|---|---|---| +| 1.1 | ارتقاء `CustomMudTheme.cs` — اضافه کردن PaletteDark، LayoutProperties، رنگ‌های Secondary/Tertiary/Info، تغییر Primary به `#6366f1` | `CustomMudTheme.cs` | UI | +| 1.2 | توسعه CSS Variables — اضافه کردن brand colors، semantic colors، soft backgrounds، gradients | `site.css` | UI | +| 1.3 | ساخت `` component واحد | `Shared/LoadingState.razor` (جدید) | UX | +| 1.4 | ساخت `` component واحد | `Shared/EmptyState.razor` (جدید) | UX | +| 1.5 | بهبود `` — اضافه کردن آیکون، subtitle اختیاری | `Shared/PageHeader.razor` | UI | +| 1.6 | اضافه کردن `.page-container` CSS pattern | `site.css` | UI | + +--- + +### 🔷 فاز ۲ — یکپارچه‌سازی صفحات Profile (UI ~10%, UX ~5%) +> **اولویت**: بالا | **ریسک**: پایین | **حجم**: ۹ فایل + +| # | تسک | تغییرات | +|---|---|---| +| 2.1 | `Profile/Personal` → جایگزینی header دستی با `` | UX | +| 2.2 | `Profile/Addresses` → `` + `` + `` | UX | +| 2.3 | `Profile/Wallet` → `` | UX | +| 2.4 | `Profile/Settings` → `` | UX | +| 2.5 | `Profile/Tree` → `` | UX | +| 2.6 | `Profile/WithdrawalRequests` → `` + `` | UX | +| 2.7 | `Profile/ChangePassword` → `` | UX | +| 2.8 | `Profile/Index` (Dashboard) → حذف inline styles، استفاده از CSS Variables | UI | +| 2.9 | `Club/MembershipPage` + `Club/FeaturesPage` → `` + `` | UX | + +--- + +### 🔷 فاز ۳ — یکپارچه‌سازی صفحات تخصصی (UI ~5%, UX ~5%) +> **اولویت**: متوسط | **ریسک**: پایین | **حجم**: ۵ فایل + +| # | تسک | تغییرات | +|---|---|---| +| 3.1 | `Commission/Dashboard` → `` + `` | UX | +| 3.2 | `Commission/WeeklyBalance` → `` + حذف inline gradient styles | UI + UX | +| 3.3 | `Network/NetworkStatistics` → `` + `` | UX | +| 3.4 | `PackageDetail` → wrap loading/error در `MudContainer` | UX bug fix | +| 3.5 | `Checkout` → Elevation=4→2، حذف inline radial-gradient | UI | + +--- + +### 🔷 فاز ۴ — بهبود بصری فروشگاه‌ها (UI ~10%) +> **اولویت**: متوسط | **ریسک**: پایین | **حجم**: ۶ فایل + +| # | تسک | تغییرات | +|---|---|---| +| 4.1 | `Store/Products` → حذف inline hero styles، استفاده از CSS class | UI | +| 4.2 | `Store/ProductDetail` → حذف inline image styles، ساخت `.product-image-main` CSS | UI | +| 4.3 | `DiscountStore/ProductDetail` → حذف hardcoded rgba، استفاده از `--ds-success-soft` | UI | +| 4.4 | `Gateway/*` (۳ صفحه) → حذف hardcoded `#10b981`/`#ef4444`، استفاده از `--ds-color-store`/`--ds-color-discount` | UI | +| 4.5 | یکسان‌سازی Elevation → `0` با border یا `2` | UI | +| 4.6 | یکسان‌سازی Container spacing → `py-6` | UI | + +--- + +### 🔷 فاز ۵ — بهبود صفحات عمومی و بلاگ (UI ~10%) +> **اولویت**: پایین | **ریسک**: پایین | **حجم**: ۷ فایل + +| # | تسک | تغییرات | +|---|---|---| +| 5.1 | `Index.razor` → حذف inline styles از hero، استفاده از CSS class | UI | +| 5.2 | `About.razor` → حذف inline radial-gradient | UI | +| 5.3 | `Contact.razor` → cleanup minor inline styles | UI | +| 5.4 | `FAQ.razor` → cleanup minor inline styles | UI | +| 5.5 | `Blog/Index` → حذف inline hero styles، استفاده از CSS class | UI | +| 5.6 | `Blog/Post` → حذف inline styles از hero image و typography | UI | +| 5.7 | `RegisterWizard` → بهینه‌سازی wizard-section dark mode | UI | + +--- + +### 🔷 فاز ۶ — Polish نهایی و فرآیندی (UX ~10%) +> **اولویت**: پایین | **ریسک**: بسیار پایین | **حجم**: ۴ فایل + تست + +| # | تسک | تغییرات | +|---|---|---| +| 6.1 | Fix routing inconsistency — Categories + Blog/Post | UX | +| 6.2 | MudSnackbar notifications styling | UI | +| 6.3 | Dialog styling consistency (AuthDialog, AddressDialogs) | UI | +| 6.4 | Micro-interactions — button press, card hover, page transition | UI | +| 6.5 | تست کامل Dark Mode در تمام صفحات | UI + QA | +| 6.6 | تست Mobile Responsive در تمام صفحات | UX + QA | + +--- + +## 📈 بخش ۴: جدول تأثیرگذاری + +### تأثیر UI (هدف ~۵۰٪ تغییر) + +| حوزه | تعداد فایل | درصد تأثیر | +|---|---|---| +| Theme + CSS Variables | ۲ | ۱۵% (تأثیر سراسری) | +| حذف Inline Styles | ۱۱ | ۱۵% | +| یکسان‌سازی Elevation/Rounding | ۲۰+ | ۱۰% | +| بهبود رنگ‌بندی (CSS Variables) | ۸ | ۵% | +| Micro-interactions | سراسری | ۵% | +| **جمع** | | **~۵۰%** | + +### تأثیر UX (هدف ~۲۰٪ تغییر) + +| حوزه | تعداد فایل | درصد تأثیر | +|---|---|---| +| PageHeader یکپارچه | ۱۲ صفحه | ۸% | +| LoadingState واحد | ۱۵+ صفحه | ۴% | +| EmptyState واحد | ۸+ صفحه | ۳% | +| Container fixes (PackageDetail) | ۲ | ۲% | +| Routing consistency | ۲ | ۱% | +| Flow improvements | ۲ | ۲% | +| **جمع** | | **~۲۰%** | + +--- + +## 🔧 بخش ۵: مشخصات فنی کامپوننت‌های جدید + +### ۵.۱ LoadingState Component + +```razor +@* Shared/LoadingState.razor *@ + + + @if (!string.IsNullOrWhiteSpace(Message)) + { + @Message + } + + +@code { + [Parameter] public string Message { get; set; } = "در حال بارگذاری..."; +} +``` + +### ۵.۲ EmptyState Component + +```razor +@* Shared/EmptyState.razor *@ + + + @Title + @if (!string.IsNullOrWhiteSpace(Description)) + { + + @Description + + } + @if (!string.IsNullOrWhiteSpace(ActionText)) + { + + @ActionText + + } + + +@code { + [Parameter] public string Icon { get; set; } = Icons.Material.Filled.Inbox; + [Parameter] public string Title { get; set; } = "موردی یافت نشد"; + [Parameter] public string? Description { get; set; } + [Parameter] public string? ActionText { get; set; } + [Parameter] public string? ActionHref { get; set; } + [Parameter] public EventCallback OnAction { get; set; } +} +``` + +### ۵.۳ PageHeader ارتقاء‌یافته + +```razor +@* Shared/PageHeader.razor — ارتقاء‌یافته *@ + + +@code { + [Parameter] public string Title { get; set; } = ""; + [Parameter] public string? Subtitle { get; set; } + [Parameter] public string? BackHref { get; set; } + + [Inject] private IJSRuntime JS { get; set; } = default!; + private async Task GoBack() => await JS.InvokeVoidAsync("history.back"); +} +``` + +--- + +## ✅ بخش ۶: چک‌لیست تکمیل هر فاز + +### فاز ۱ چک‌لیست: +- [ ] `CustomMudTheme.cs` — Primary→`#6366f1`, PaletteDark اضافه شد +- [ ] `site.css` — CSS Variables جدید (brand, semantic, soft, gradient) +- [ ] `Shared/LoadingState.razor` — ساخته و تست شد +- [ ] `Shared/EmptyState.razor` — ساخته و تست شد +- [ ] `Shared/PageHeader.razor` — Subtitle parameter اضافه شد +- [ ] `.page-container` CSS pattern اضافه شد +- [ ] Build: 0 errors ✅ +- [ ] Dark Mode: صحیح ✅ +- [ ] Mobile: صحیح ✅ + +### فاز ۲ چک‌لیست: +- [ ] `Profile/Personal` → `` +- [ ] `Profile/Addresses` → `` + `` + `` +- [ ] `Profile/Wallet` → `` +- [ ] `Profile/Settings` → `` +- [ ] `Profile/Tree` → `` +- [ ] `Profile/WithdrawalRequests` → `` + `` +- [ ] `Profile/ChangePassword` → `` +- [ ] `Profile/Index` → inline styles → CSS +- [ ] `Club/*` → `` + `` +- [ ] Build: 0 errors ✅ + +### فاز ۳ چک‌لیست: +- [ ] `Commission/*` → `` + `` +- [ ] `Network/*` → `` + `` +- [ ] `PackageDetail` → MudContainer wrapper برای loading/error +- [ ] `Checkout` → Elevation fix + inline cleanup +- [ ] `WeeklyBalance` → inline gradient → CSS class +- [ ] Build: 0 errors ✅ + +### فاز ۴ چک‌لیست: +- [ ] `Store/Products` → hero inline → CSS +- [ ] `Store/ProductDetail` → image inline → CSS class +- [ ] `DiscountStore/ProductDetail` → rgba → variable +- [ ] `Gateway/*` → hardcoded → variable +- [ ] Elevation یکسان‌سازی +- [ ] Container spacing یکسان‌سازی +- [ ] Build: 0 errors ✅ + +### فاز ۵ چک‌لیست: +- [ ] `Index.razor` → hero inline cleanup +- [ ] `About.razor` → radial-gradient cleanup +- [ ] `Blog/Index` + `Blog/Post` → inline cleanup +- [ ] `Contact.razor` + `FAQ.razor` → minor cleanup +- [ ] Build: 0 errors ✅ + +### فاز ۶ چک‌لیست: +- [ ] Routing fix (Categories, Blog/Post) +- [ ] MudSnackbar styling +- [ ] Dialog consistency +- [ ] Dark mode full test +- [ ] Mobile responsive full test +- [ ] Build: 0 errors ✅ + +--- + +## 📋 بخش ۷: خلاصه تغییرات در یک نگاه + +``` +فایل‌های تغییر‌یافته: +├── CustomMudTheme.cs [فاز ۱] — ارتقاء کامل تم +├── site.css [فاز ۱-۵] — CSS Variables + class‌های جدید +├── Shared/LoadingState.razor [فاز ۱] — جدید +├── Shared/EmptyState.razor [فاز ۱] — جدید +├── Shared/PageHeader.razor [فاز ۱] — بهبود +├── Profile/* (۷ فایل) [فاز ۲] — PageHeader + LoadingState +├── Profile/Index.razor [فاز ۲] — حذف inline styles +├── Club/* (۲ فایل) [فاز ۲] — PageHeader + LoadingState +├── Commission/* (۲ فایل) [فاز ۳] — PageHeader + LoadingState + cleanup +├── Network/* (۱ فایل) [فاز ۳] — PageHeader + LoadingState +├── PackageDetail.razor [فاز ۳] — Container fix +├── Checkout.razor [فاز ۳] — Elevation + cleanup +├── Store/* (۲ فایل) [فاز ۴] — inline → CSS +├── DiscountStore/ProductDetail [فاز ۴] — rgba → variable +├── Gateway/* (۳ فایل) [فاز ۴] — hardcoded → variable +├── Index.razor [فاز ۵] — hero cleanup +├── About.razor [فاز ۵] — gradient cleanup +├── Blog/* (۲ فایل) [فاز ۵] — inline cleanup +├── Contact.razor + FAQ.razor [فاز ۵] — minor cleanup +└── Categories + Blog/Post [فاز ۶] — routing fix +``` + +**مجموع فایل‌های تأثیرپذیر**: ~۳۵ فایل +**فایل‌های جدید**: ۲ (LoadingState, EmptyState) +**میزان تغییر UI**: ~۵۰٪ +**میزان تغییر UX**: ~۲۰٪ +**ریسک شکست**: پایین (تغییرات تدریجی، build verification در هر فاز) diff --git a/src/FrontOffice.Main/ConfigureServices.cs b/src/FrontOffice.Main/ConfigureServices.cs index 76ab52e..0f1c61a 100644 --- a/src/FrontOffice.Main/ConfigureServices.cs +++ b/src/FrontOffice.Main/ConfigureServices.cs @@ -21,6 +21,13 @@ using CMSMicroservice.Protobuf.Protos.Configuration; using CMSMicroservice.Protobuf.Protos.NetworkMembership; using CMSMicroservice.Protobuf.Protos.Commission; using CMSMicroservice.Protobuf.Protos.AppVersion; +using CMSMicroservice.Protobuf.Protos.SitePage; +using CMSMicroservice.Protobuf.Protos.BlogPost; +using CMSMicroservice.Protobuf.Protos.BlogCategory; +using CMSMicroservice.Protobuf.Protos.DiscountProduct; +using CMSMicroservice.Protobuf.Protos.DiscountCategory; +using CMSMicroservice.Protobuf.Protos.DiscountShoppingCart; +using CMSMicroservice.Protobuf.Protos.DiscountOrder; using FrontOffice.Main.Utilities; namespace Microsoft.Extensions.DependencyInjection; @@ -66,6 +73,16 @@ public static class ConfigureServices services.AddScoped(); // Main Service for core functionality services.AddScoped(); + // Site Page service for dynamic content (About, Contact) + services.AddScoped(); + // Blog Post service for landing page latest posts & blog pages + services.AddScoped(); + // Blog Category service for blog sidebar filters + services.AddScoped(); + // Discount Store services + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); // Device detection: very light, dependency-free services.AddTransient(); // PDF generation (Chromium only) @@ -115,6 +132,17 @@ public static class ConfigureServices services.AddScoped(CreateAuthenticatedClient); services.AddScoped(CreateAuthenticatedClient); services.AddScoped(CreateAuthenticatedClient); + services.AddScoped(CreateAuthenticatedClient); + services.AddScoped(CreateAuthenticatedClient); + services.AddScoped(CreateAuthenticatedClient); + // Image Resolver Service + services.AddScoped(CreateAuthenticatedClient); + services.AddScoped(); + // Discount Store gRPC clients + services.AddScoped(CreateAuthenticatedClient); + services.AddScoped(CreateAuthenticatedClient); + services.AddScoped(CreateAuthenticatedClient); + services.AddScoped(CreateAuthenticatedClient); return services; } diff --git a/src/FrontOffice.Main/Pages/About.razor b/src/FrontOffice.Main/Pages/About.razor index b6ddbc0..57485ba 100644 --- a/src/FrontOffice.Main/Pages/About.razor +++ b/src/FrontOffice.Main/Pages/About.razor @@ -1,192 +1,280 @@ @attribute [Route(RouteConstants.About.Index)] @inject NavigationManager Navigation +@inject SitePageService SitePageService درباره ما | کارا بازار سلامت - -
- - - - درباره کارا بازار سلامت - - - پلتفرم هوشمند تیم‌سازی و مدیریت فروش - - - ما با ارائه ابزارهای نوآورانه، به کسب‌وکارها کمک می‌کنیم تا تیم‌های فروش خود را گسترش دهند و به صورت کارآمد مدیریت کنند. - - - - شروع کنید - - - تماس با ما - - - - - - - - - - +@if (_loading) +{ + + -
- - - - -
+} +else +{ + +
- چشم‌انداز و مأموریت ما - + - - - چشم‌انداز - - تبدیل شدن به پیشروترین پلتفرم مدیریت تیم‌های فروش در منطقه، با ارائه راهکارهای هوشمند و کاربرپسند برای کسب‌وکارهای کوچک و بزرگ. + درباره کارا بازار سلامت + + + @(_page?.HeroTitle ?? "پلتفرم هوشمند تیم‌سازی و مدیریت فروش") - + + @(_page?.HeroSubtitle ?? "ما با ارائه ابزارهای نوآورانه، به کسب‌وکارها کمک می‌کنیم تا تیم‌های فروش خود را گسترش دهند و به صورت کارآمد مدیریت کنند.") + + + + شروع کنید + + + تماس با ما + + + - - - مأموریت - - توانمندسازی کسب‌وکارها از طریق فناوری‌های نوین، ساده‌سازی فرآیندهای پیچیده و ایجاد کارا بازار سلامت‌های جدید برای رشد و توسعه پایدار. - + +
- -
- - ارزش‌های ما - - - - - صداقت و شفافیت - - در تمامی تعاملات خود با مشتریان و همکاران، صداقت و شفافیت را سرلوحه کار خود قرار داده‌ایم. - - - - - - - نوآوری - - همواره در پی یافتن راهکارهای جدید و بهبود فرآیندها هستیم تا بهترین تجربه را برای کاربران فراهم کنیم. - - - - - - - مشتری‌مداری - - رضایت و موفقیت مشتریان اولویت اصلی ما است و تمامی تصمیمات خود را بر اساس نیازهای آنها می‌گیریم. - - - - - - - کیفیت - - به کیفیت محصولات و خدمات خود افتخار می‌کنیم و همواره استانداردهای بالایی را رعایت می‌کنیم. - - - - - - - پاسخگویی - - به سرعت به نیازهای مشتریان پاسخ می‌دهیم و پشتیبانی ۲۴ ساعته را فراهم کرده‌ایم. - - - - - - - پایداری - - به دنبال ایجاد ارزش بلندمدت برای مشتریان و جامعه هستیم و به توسعه پایدار متعهد هستیم. - - - - - -
+ - -
- - تیم ما - - - - - - - علی رضایی - مدیرعامل - - بیش از ۱۰ سال تجربه در حوزه فناوری و مدیریت کسب‌وکار - - - - - - - - - مریم احمدی - مدیر محصول - - متخصص در طراحی تجربه کاربری و توسعه محصول - - - - - - - - - حسن کریمی - مدیر فنی - - مهندس نرم‌افزار با تخصص در معماری سیستم‌های توزیع‌شده - - - - - -
-
+ +
+ + چشم‌انداز و مأموریت ما + + + + + + @(_visionSection?.Title ?? "چشم‌انداز") + + @if (!string.IsNullOrWhiteSpace(_visionSection?.HtmlContent)) + { + + @((MarkupString)_visionSection.HtmlContent) + + } + else + { + + تبدیل شدن به پیشروترین پلتفرم مدیریت تیم‌های فروش در منطقه، با ارائه راهکارهای هوشمند و کاربرپسند برای کسب‌وکارهای کوچک و بزرگ. + + } + + + + + + + @(_missionSection?.Title ?? "مأموریت") + + @if (!string.IsNullOrWhiteSpace(_missionSection?.HtmlContent)) + { + + @((MarkupString)_missionSection.HtmlContent) + + } + else + { + + توانمندسازی کسب‌وکارها از طریق فناوری‌های نوین، ساده‌سازی فرآیندهای پیچیده و ایجاد کارا بازار سلامت‌های جدید برای رشد و توسعه پایدار. + + } + + + + +
+ + +
+ + ارزش‌های ما + + @if (_valueSections.Any()) + { + @foreach (var value in _valueSections) + { + + + @if (!string.IsNullOrWhiteSpace(value.IconName)) + { + + } + @value.Title + @if (!string.IsNullOrWhiteSpace(value.HtmlContent)) + { + + @((MarkupString)value.HtmlContent) + + } + + + } + } + else + { + @* Fallback: hardcoded values *@ + + + + صداقت و شفافیت + + در تمامی تعاملات خود با مشتریان و همکاران، صداقت و شفافیت را سرلوحه کار خود قرار داده‌ایم. + + + + + + + نوآوری + + همواره در پی یافتن راهکارهای جدید و بهبود فرآیندها هستیم تا بهترین تجربه را برای کاربران فراهم کنیم. + + + + + + + مشتری‌مداری + + رضایت و موفقیت مشتریان اولویت اصلی ما است و تمامی تصمیمات خود را بر اساس نیازهای آنها می‌گیریم. + + + + + + + کیفیت + + به کیفیت محصولات و خدمات خود افتخار می‌کنیم و همواره استانداردهای بالایی را رعایت می‌کنیم. + + + + + + + پاسخگویی + + به سرعت به نیازهای مشتریان پاسخ می‌دهیم و پشتیبانی ۲۴ ساعته را فراهم کرده‌ایم. + + + + + + + پایداری + + به دنبال ایجاد ارزش بلندمدت برای مشتریان و جامعه هستیم و به توسعه پایدار متعهد هستیم. + + + + } + + +
+ + +
+ + تیم ما + + @if (_teamSections.Any()) + { + @foreach (var member in _teamSections) + { + + + + @if (!string.IsNullOrWhiteSpace(member.ImagePath)) + { + + } + else + { + + } + + @member.Title + @if (!string.IsNullOrWhiteSpace(member.Subtitle)) + { + @member.Subtitle + } + @if (!string.IsNullOrWhiteSpace(member.HtmlContent)) + { + + @((MarkupString)member.HtmlContent) + + } + + + } + } + else + { + @* Fallback: hardcoded team *@ + + + + + + علی رضایی + مدیرعامل + + بیش از ۱۰ سال تجربه در حوزه فناوری و مدیریت کسب‌وکار + + + + + + + + + مریم احمدی + مدیر محصول + + متخصص در طراحی تجربه کاربری و توسعه محصول + + + + + + + + + حسن کریمی + مدیر فنی + + مهندس نرم‌افزار با تخصص در معماری سیستم‌های توزیع‌شده + + + + } + + +
+ +} diff --git a/src/FrontOffice.Main/Pages/About.razor.cs b/src/FrontOffice.Main/Pages/About.razor.cs index 59858ec..af24e4b 100644 --- a/src/FrontOffice.Main/Pages/About.razor.cs +++ b/src/FrontOffice.Main/Pages/About.razor.cs @@ -1,8 +1,37 @@ +using FrontOffice.Main.Utilities; + namespace FrontOffice.Main.Pages; + public partial class About { - private void ScrollToContact() + private bool _loading = true; + private SitePageDto? _page; + private SitePageSectionDto? _visionSection; + private SitePageSectionDto? _missionSection; + private List _valueSections = new(); + private List _teamSections = new(); + + protected override async Task OnInitializedAsync() { - // TODO: Implement smooth scroll to contact section + try + { + _page = await SitePageService.GetByKeyAsync("about"); + + if (_page != null) + { + _visionSection = _page.GetSection("vision"); + _missionSection = _page.GetSection("mission"); + _valueSections = _page.GetSections("value-").ToList(); + _teamSections = _page.GetSections("team-").ToList(); + } + } + catch + { + // Fallback: page remains null → hardcoded content will render + } + finally + { + _loading = false; + } } } \ No newline at end of file diff --git a/src/FrontOffice.Main/Pages/Blog/Index.razor b/src/FrontOffice.Main/Pages/Blog/Index.razor new file mode 100644 index 0000000..3dc07b7 --- /dev/null +++ b/src/FrontOffice.Main/Pages/Blog/Index.razor @@ -0,0 +1,172 @@ +@attribute [Route(RouteConstants.Blog.Index)] + +بلاگ | کارا بازار سلامت + +@* ═══════════════════════════════════════════════ + BLOG HEADER + ═══════════════════════════════════════════════ *@ +
+ +
+ بلاگ + + آخرین مطالب، آموزش‌ها و اخبار کارا بازار سلامت + +
+
+
+ +@* ═══════════════════════════════════════════════ + SEARCH + FILTERS + ═══════════════════════════════════════════════ *@ + + + + + + + + + + @foreach (var cat in _categories) + { + + @cat.Title + @if (cat.PostCount > 0) + { + (@cat.PostCount) + } + + } + + + + + جستجو + + + + + + @* ═══════════════════════════════════════════════ + BLOG POSTS GRID + ═══════════════════════════════════════════════ *@ + @if (_isLoading) + { + + } + else if (!_posts.Any()) + { + + + مقاله‌ای یافت نشد + + @if (!string.IsNullOrWhiteSpace(_searchTerm) || _selectedCategoryId.HasValue) + { + فیلترها را تغییر دهید یا عبارت دیگری جستجو کنید. + } + else + { + به‌زودی مقالات جدید منتشر خواهد شد. + } + + + } + else + { + + @foreach (var post in _posts) + { + + + @* Thumbnail *@ +
+ @if (!string.IsNullOrWhiteSpace(post.ThumbnailUrl)) + { + + } + else + { +
+ +
+ } +
+ + @* Content *@ +
+ @if (post.Categories.Any()) + { + + @post.Categories.First().Title + + } + + + @post.Title + + + @if (!string.IsNullOrWhiteSpace(post.Summary)) + { + + @post.Summary + + } + + + @if (post.PublishedAt.HasValue) + { + + + @post.PublishedAt.Value.ToString("yyyy/MM/dd") + + } + + + + @post.ViewCount.ToString("N0") + + +
+
+
+ } +
+ + @* ── Pagination ── *@ + @if (_totalPages > 1) + { +
+ +
+ } + } + +
diff --git a/src/FrontOffice.Main/Pages/Blog/Index.razor.cs b/src/FrontOffice.Main/Pages/Blog/Index.razor.cs new file mode 100644 index 0000000..271b4c8 --- /dev/null +++ b/src/FrontOffice.Main/Pages/Blog/Index.razor.cs @@ -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 _posts = new(); + private List _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}"); + } +} diff --git a/src/FrontOffice.Main/Pages/Blog/Post.razor b/src/FrontOffice.Main/Pages/Blog/Post.razor new file mode 100644 index 0000000..0878173 --- /dev/null +++ b/src/FrontOffice.Main/Pages/Blog/Post.razor @@ -0,0 +1,122 @@ +@page "/blog/{Slug}" + +@(_post?.Title ?? "مقاله") | بلاگ + +@if (_isLoading) +{ + + + +} +else if (_post == null) +{ + + + +} +else +{ + @* ═══ Featured Image Header ═══ *@ + @if (!string.IsNullOrWhiteSpace(_post.FeaturedImagePath)) + { +
+ +
+
+ } + + + @* ── Breadcrumb ── *@ + + خانه + + بلاگ + + @_post.Title + + + @* ── Article Header ── *@ + + @if (_post.Categories.Any()) + { + + @foreach (var cat in _post.Categories) + { + + @cat.Title + + } + + } + + + @_post.Title + + + @if (!string.IsNullOrWhiteSpace(_post.Summary)) + { + + @_post.Summary + + } + + + @if (_post.PublishedAt.HasValue) + { + + + + @_post.PublishedAt.Value.ToString("yyyy/MM/dd") + + + } + + + + @_post.ViewCount.ToString("N0") بازدید + + + + + + + + @* ── Article Content ── *@ + + @((MarkupString)_post.HtmlContent) + + + @* ── Tags ── *@ + @if (_post.Tags.Any()) + { + + + + @foreach (var tag in _post.Tags) + { + + @tag.Title + + } + + } + + @* ── Back to Blog ── *@ +
+ + بازگشت به بلاگ + +
+
+} diff --git a/src/FrontOffice.Main/Pages/Blog/Post.razor.cs b/src/FrontOffice.Main/Pages/Blog/Post.razor.cs new file mode 100644 index 0000000..3a4240b --- /dev/null +++ b/src/FrontOffice.Main/Pages/Blog/Post.razor.cs @@ -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"); +} diff --git a/src/FrontOffice.Main/Pages/Checkout.razor b/src/FrontOffice.Main/Pages/Checkout.razor index 0c3e800..6662eb7 100644 --- a/src/FrontOffice.Main/Pages/Checkout.razor +++ b/src/FrontOffice.Main/Pages/Checkout.razor @@ -5,10 +5,11 @@ تکمیل خرید + - + @@ -23,7 +24,7 @@ - + جزئیات پکیج @if (_selectedPackage != null) @@ -33,9 +34,9 @@ - - + انتخاب آدرس @if (_isLoadingAddresses) @@ -149,7 +150,7 @@ - + کد تخفیف @if (_selectedPackage != null) { - + خلاصه سفارش diff --git a/src/FrontOffice.Main/Pages/Checkout.razor.cs b/src/FrontOffice.Main/Pages/Checkout.razor.cs index 6a4813e..b132c8d 100644 --- a/src/FrontOffice.Main/Pages/Checkout.razor.cs +++ b/src/FrontOffice.Main/Pages/Checkout.razor.cs @@ -59,7 +59,7 @@ public partial class Checkout Id: response.Id, Title: response.Title, Body: response.Description, - Image: UrlUtility.DownloadUrl + response.ImagePath, + Image: response.ImagePath ?? string.Empty, Price: response.Price ); _finalPrice = _selectedPackage.Price; diff --git a/src/FrontOffice.Main/Pages/Club/FeaturesPage.razor b/src/FrontOffice.Main/Pages/Club/FeaturesPage.razor index 8e9768b..3e4bc21 100644 --- a/src/FrontOffice.Main/Pages/Club/FeaturesPage.razor +++ b/src/FrontOffice.Main/Pages/Club/FeaturesPage.razor @@ -3,15 +3,13 @@ @using MudBlazor @inject ClubConfigurationService ClubConfigService @inject IDialogService DialogService +@inject IJSRuntime JS ویژگی‌های باشگاه مشتریان - - ویژگی‌های باشگاه مشتریان - بازگشت - + @@ -30,13 +28,13 @@ @if (_isLoading) { - + } else if (_features == null || !_features.Any()) { - - هنوز ویژگی‌ای برای شما فعال نشده است. - + } else { @@ -73,7 +71,7 @@ FullWidth="true" Size="Size.Large" StartIcon="@Icons.Material.Filled.ArrowBack" - Href="@RouteConstants.Club.Membership"> + OnClick="GoBack"> بازگشت به صفحه باشگاه @@ -190,4 +188,6 @@ _selectedFeature = feature; _showDetailDialog = true; } + + private async Task GoBack() => await JS.InvokeVoidAsync("history.back"); } diff --git a/src/FrontOffice.Main/Pages/Club/MembershipPage.razor b/src/FrontOffice.Main/Pages/Club/MembershipPage.razor index 55784f1..a788171 100644 --- a/src/FrontOffice.Main/Pages/Club/MembershipPage.razor +++ b/src/FrontOffice.Main/Pages/Club/MembershipPage.razor @@ -7,14 +7,11 @@ - - عضویت باشگاه مشتریان - بازگشت - + @if (_isLoading) { - + } else if (_membership is not null) { diff --git a/src/FrontOffice.Main/Pages/Commission/CommissionDashboardPage.razor b/src/FrontOffice.Main/Pages/Commission/CommissionDashboardPage.razor index c9bf44a..bd0dda9 100644 --- a/src/FrontOffice.Main/Pages/Commission/CommissionDashboardPage.razor +++ b/src/FrontOffice.Main/Pages/Commission/CommissionDashboardPage.razor @@ -8,10 +8,7 @@ - - پاداش‌های من - بازگشت - + @@ -52,7 +49,7 @@ @if (_isLoading) { - + } else { diff --git a/src/FrontOffice.Main/Pages/Commission/WeeklyBalancePage.razor b/src/FrontOffice.Main/Pages/Commission/WeeklyBalancePage.razor index 1dc2cfe..0e7d9cb 100644 --- a/src/FrontOffice.Main/Pages/Commission/WeeklyBalancePage.razor +++ b/src/FrontOffice.Main/Pages/Commission/WeeklyBalancePage.razor @@ -7,10 +7,7 @@ - - گزارش هفتگی پاداش - بازگشت - + @@ -50,7 +47,7 @@ @if (_isLoading) { - + } else if (_weeklyBalance is not null) { @@ -76,11 +73,11 @@ - + تیم دوم - @_weeklyBalance.RightBalanceFormatted + @_weeklyBalance.RightBalanceFormatted - + تیم اول - @_weeklyBalance.LeftBalanceFormatted + @_weeklyBalance.LeftBalanceFormatted - محاسبات پاداش + محاسبات پاداش diff --git a/src/FrontOffice.Main/Pages/Contact.razor b/src/FrontOffice.Main/Pages/Contact.razor index c98a984..ed61fa4 100644 --- a/src/FrontOffice.Main/Pages/Contact.razor +++ b/src/FrontOffice.Main/Pages/Contact.razor @@ -1,4 +1,5 @@ @attribute [Route(RouteConstants.Contact.Index)] +@inject SitePageService SitePageService ارتباط با ما | کارا بازار سلامت @@ -8,10 +9,10 @@ ارتباط با ما - آماده شنیدن صدای شما هستیم + @(_page?.HeroTitle ?? "آماده شنیدن صدای شما هستیم") - سوالات، پیشنهادات یا انتقادات خود را با ما در میان بگذارید. تیم ما آماده پاسخگویی به شماست. + @(_page?.HeroSubtitle ?? "سوالات، پیشنهادات یا انتقادات خود را با ما در میان بگذارید. تیم ما آماده پاسخگویی به شماست.") @@ -25,7 +26,7 @@ - + فرم تماس @@ -72,12 +73,12 @@ Variant="Variant.Outlined" Required="true" RequiredError="انتخاب موضوع الزامی است."> - عمومی - پشتیبانی فنی - فروش و قیمت‌گذاری - شریک تجاری - شکایت - پیشنهاد + عمومی + پشتیبانی فنی + فروش و قیمت‌گذاری + شریک تجاری + شکایت + پیشنهاد @@ -123,7 +124,7 @@ - + اطلاعات تماس @@ -132,7 +133,7 @@
آدرس - کرج مهرویلا میدان مادر ساختمان بزرگمهر طبقه ۴ واحد ۱۶ + @(_contactAddress ?? "کرج مهرویلا میدان مادر ساختمان بزرگمهر طبقه ۴ واحد ۱۶")
@@ -141,7 +142,7 @@
تلفن - 026-34233563 + @(_contactPhone ?? "026-34233563")
@@ -149,7 +150,7 @@
ایمیل - info@kbs1.co + @(_contactEmail ?? "info@kbs1.co")
@@ -158,8 +159,7 @@
ساعات کاری - شنبه تا پنج‌شنبه
- ۹ صبح تا ۶ عصر + @((MarkupString)(_contactHours ?? "شنبه تا پنج‌شنبه
۹ صبح تا ۶ عصر"))
@@ -167,14 +167,14 @@
- + شبکه‌های اجتماعی تلگرام @@ -183,7 +183,7 @@ اینستاگرام @@ -192,7 +192,7 @@ لینکدین @@ -201,7 +201,7 @@ واتس‌اپ @@ -219,14 +219,14 @@ موقعیت مکانی - + -
+
نقشه موقعیت مکانی - کرج مهرویلا میدان مادر ساختمان بزرگمهر طبقه ۴ واحد ۱۶ + @(_contactAddress ?? "کرج مهرویلا میدان مادر ساختمان بزرگمهر طبقه ۴ واحد ۱۶")
diff --git a/src/FrontOffice.Main/Pages/Contact.razor.cs b/src/FrontOffice.Main/Pages/Contact.razor.cs index 4c81289..d44c34c 100644 --- a/src/FrontOffice.Main/Pages/Contact.razor.cs +++ b/src/FrontOffice.Main/Pages/Contact.razor.cs @@ -1,8 +1,10 @@ using FluentValidation; +using FrontOffice.Main.Utilities; using MudBlazor; using Severity = MudBlazor.Severity; namespace FrontOffice.Main.Pages; + public partial class Contact { private ContactForm _contactForm = new(); @@ -10,6 +12,60 @@ public partial class Contact private bool _isSubmitting; private readonly ContactFormValidator _contactFormValidator = new(); + // Dynamic CMS data + private SitePageDto? _page; + private string? _contactAddress; + private string? _contactPhone; + private string? _contactEmail; + private string? _contactHours; + private string? _socialTelegram; + private string? _socialInstagram; + private string? _socialLinkedin; + private string? _socialWhatsapp; + + protected override async Task OnInitializedAsync() + { + try + { + _page = await SitePageService.GetByKeyAsync("contact"); + + if (_page != null) + { + // Load contact info from CMS section + var contactInfoSection = _page.GetSection("contact-info"); + if (contactInfoSection != null) + { + var contactData = contactInfoSection.GetExtraData(); + if (contactData != null) + { + _contactAddress = contactData.Address; + _contactPhone = contactData.Phone; + _contactEmail = contactData.Email; + _contactHours = contactData.Hours; + } + } + + // Load social media links from CMS section + var socialSection = _page.GetSection("social-media"); + if (socialSection != null) + { + var socialData = socialSection.GetExtraData(); + if (socialData != null) + { + _socialTelegram = socialData.Telegram; + _socialInstagram = socialData.Instagram; + _socialLinkedin = socialData.Linkedin; + _socialWhatsapp = socialData.Whatsapp; + } + } + } + } + catch + { + // Fallback: all values remain null → hardcoded defaults will render + } + } + private async Task SubmitContactForm() { if (_form is null) return; @@ -55,14 +111,12 @@ public partial class Contact private void CallSupport() { - // TODO: Initiate phone call or show phone number - Snackbar.Add("شماره تماس: ۰۲۱-۱۲۳۴۵۶۷۸", Severity.Info); + Snackbar.Add($"شماره تماس: {_contactPhone ?? "026-34233563"}", Severity.Info); } private void SendEmail() { - // TODO: Open email client or redirect to email page - Snackbar.Add("ایمیل: info@kbs1.co", Severity.Info); + Snackbar.Add($"ایمیل: {_contactEmail ?? "info@kbs1.co"}", Severity.Info); } public class ContactForm @@ -87,4 +141,21 @@ public partial class Contact RuleFor(x => x.Message).NotEmpty().MinimumLength(10).WithMessage("پیام باید حداقل ۱۰ کاراکتر باشد"); } } + + // DTO classes for ExtraData JSON deserialization + private class ContactInfoData + { + public string? Address { get; set; } + public string? Phone { get; set; } + public string? Email { get; set; } + public string? Hours { get; set; } + } + + private class SocialMediaData + { + public string? Telegram { get; set; } + public string? Instagram { get; set; } + public string? Linkedin { get; set; } + public string? Whatsapp { get; set; } + } } \ No newline at end of file diff --git a/src/FrontOffice.Main/Pages/DiscountStore/Cart.razor b/src/FrontOffice.Main/Pages/DiscountStore/Cart.razor new file mode 100644 index 0000000..72f705b --- /dev/null +++ b/src/FrontOffice.Main/Pages/DiscountStore/Cart.razor @@ -0,0 +1,190 @@ +@attribute [Route(RouteConstants.DiscountStore.Cart)] + +سبد خرید تخفیفی + + + + + + @if (DiscountCart.Items.Count == 0) + { + سبد خرید تخفیفی شما خالی است. + + بازگشت به فروشگاه تخفیفی + + } + else + { + + + + + + محصول + قیمت واحد + سقف تخفیف + تعداد + تخفیف + قیمت نهایی + + + + + + + @context.Title + + + @FormatPrice(context.UnitPrice) + + + تا @context.MaxDiscountPercent% + + + + + + @context.Quantity + + + + + @if (context.DiscountAmount > 0) + { + @FormatPrice(context.DiscountAmount)- + } + else + { + + } + + @FormatPrice(context.FinalPrice) + + + + + + + + + + + + @foreach (var item in DiscountCart.Items) + { + + + + + + @item.Title + + + تا @item.MaxDiscountPercent% + + + + + + @FormatPrice(item.UnitPrice) واحد + + + @item.Quantity + + + + + @if (item.DiscountAmount > 0) + { + تخفیف: @FormatPrice(item.DiscountAmount)- + } + جمع: @FormatPrice(item.FinalPrice) + + + + + + } + + + + + @if (DeviceDetector.IsMobile()) + { + + + جمع کل: + @FormatPrice(DiscountCart.TotalPrice) تومان + + @if (DiscountCart.TotalDiscount > 0) + { + + سقف تخفیف قابل اعمال: + @FormatPrice(DiscountCart.TotalDiscount)- تومان + + } + + حداقل پرداخت: + @FormatPrice(DiscountCart.FinalPrice) تومان + + + + + + افزودن محصول + + ادامه خرید + + } + else + { + + + + جمع کل: + @FormatPrice(DiscountCart.TotalPrice) تومان + + @if (DiscountCart.TotalDiscount > 0) + { + + سقف تخفیف قابل اعمال: + @FormatPrice(DiscountCart.TotalDiscount)- تومان + + } + + + حداقل مبلغ پرداخت درگاه: + @FormatPrice(DiscountCart.FinalPrice) تومان + + + مبلغ دقیق پرداخت در مرحله تسویه مشخص خواهد شد. + + + + + + + افزودن محصول + + ادامه فرایند خرید + + } + } + + diff --git a/src/FrontOffice.Main/Pages/DiscountStore/Cart.razor.cs b/src/FrontOffice.Main/Pages/DiscountStore/Cart.razor.cs new file mode 100644 index 0000000..256e9cb --- /dev/null +++ b/src/FrontOffice.Main/Pages/DiscountStore/Cart.razor.cs @@ -0,0 +1,56 @@ +using Microsoft.AspNetCore.Components; +using FrontOffice.Main.Utilities; + +namespace FrontOffice.Main.Pages.DiscountStore; + +public partial class Cart : IDisposable +{ + [Inject] private DiscountCartService DiscountCart { get; set; } = default!; + + protected override async Task OnInitializedAsync() + { + await DiscountCart.EnsureInitializedAsync(); + DiscountCart.OnChange += StateHasChanged; + } + + private async Task IncrementQty(long productId) + { + var item = DiscountCart.Items.FirstOrDefault(i => i.ProductId == productId); + if (item is null) return; + await DiscountCart.UpdateQuantityAsync(productId, item.Quantity + 1); + } + + private async Task DecrementQty(long productId) + { + var item = DiscountCart.Items.FirstOrDefault(i => i.ProductId == productId); + if (item is null) return; + if (item.Quantity <= 1) + { + await DiscountCart.RemoveAsync(productId); + } + else + { + await DiscountCart.UpdateQuantityAsync(productId, item.Quantity - 1); + } + } + + private async Task Remove(long productId) + { + await DiscountCart.RemoveAsync(productId); + } + + private void ProceedCheckout() + { + Navigation.NavigateTo(RouteConstants.DiscountStore.Checkout); + } + + private static string FormatPrice(long price) => $"{price:N0} "; + + private static string GetImageUrl(string? imageUrl) + => string.IsNullOrWhiteSpace(imageUrl) ? "/images/product-placeholder.svg" : imageUrl.TrimStart('/'); + + public void Dispose() + { + DiscountCart.OnChange -= StateHasChanged; + } +} diff --git a/src/FrontOffice.Main/Pages/DiscountStore/Checkout.razor b/src/FrontOffice.Main/Pages/DiscountStore/Checkout.razor new file mode 100644 index 0000000..41b664a --- /dev/null +++ b/src/FrontOffice.Main/Pages/DiscountStore/Checkout.razor @@ -0,0 +1,193 @@ +@using CMSMicroservice.Protobuf.Protos.UserAddress +@attribute [Route(RouteConstants.DiscountStore.Checkout)] + +تسویه حساب فروشگاه تخفیفی + + + + + + + + + انتخاب آدرس + + @if (_loadingAddresses) + { + + + در حال بارگذاری آدرس‌ها... + + } + else if (_addresses.Count == 0) + { + + هیچ آدرسی ثبت نشده است. لطفاً از بخش پروفایل آدرس خود را اضافه کنید. + + افزودن آدرس + } + else + { + + @foreach (var address in _addresses) + { + + + + @address.Title + @address.Address + + @if (address.IsDefault) + { + پیش‌فرض + } + + + } + + } + + + + + + + پرداخت از کیف تخفیفی + + + + + موجودی کیف تخفیفی شما: + + @FormatPrice(_discountBalance) تومان + + + + + سقف مجاز تخفیف روی این سفارش: + + @FormatPrice(MaxAllowedDiscount) تومان + + + + + + + مبلغی که از کیف تخفیفی کسر شود (باقیمانده از درگاه پرداخت می‌شود): + + + + استفاده از @FormatPrice(_discountBalanceToUse) تومان + + + + + @if (_discountBalanceToUse > 0) + { + + مبلغ @FormatPrice(_discountBalanceToUse) تومان از کیف تخفیفی کسر و + مبلغ @FormatPrice(GatewayAmount) تومان از درگاه پرداخت دریافت خواهد شد. + + } + + + + + + توضیحات سفارش + + + + + + + + خلاصه سفارش + + @if (DiscountCart.Items.Count == 0) + { + سبد خرید شما خالی است. + } + else + { + + @foreach (var item in DiscountCart.Items) + { + + + + + + @item.Title + @FormatPrice(item.FinalPrice) + + + + @item.Quantity × @FormatPrice(item.UnitPrice) + + @if (item.MaxDiscountPercent > 0) + { + تا @item.MaxDiscountPercent% تخفیف + } + + + + + } + + + + + + + جمع کل: + @FormatPrice(DiscountCart.TotalPrice) + + + @if (_discountBalanceToUse > 0) + { + + از کیف تخفیفی: + @FormatPrice(_discountBalanceToUse)- + + } + + + + + پرداخت از درگاه: + + @FormatPrice(GatewayAmount) تومان + + + + + مالیات بر ارزش افزوده صرفاً بر مبلغ درگاه محاسبه خواهد شد. + + + + + @(_placing ? "در حال ثبت سفارش..." : "ثبت و پرداخت سفارش") + + } + + + + diff --git a/src/FrontOffice.Main/Pages/DiscountStore/Checkout.razor.cs b/src/FrontOffice.Main/Pages/DiscountStore/Checkout.razor.cs new file mode 100644 index 0000000..a7fa826 --- /dev/null +++ b/src/FrontOffice.Main/Pages/DiscountStore/Checkout.razor.cs @@ -0,0 +1,146 @@ +using Microsoft.AspNetCore.Components; +using CMSMicroservice.Protobuf.Protos.UserAddress; +using FrontOffice.Main.Utilities; +using MudBlazor; + +namespace FrontOffice.Main.Pages.DiscountStore; + +public partial class Checkout +{ + [Inject] private DiscountCartService DiscountCart { get; set; } = default!; + [Inject] private DiscountOrderService DiscountOrderService { get; set; } = default!; + [Inject] private UserAddressContract.UserAddressContractClient UserAddressContract { get; set; } = default!; + + private List _addresses = new(); + private CustomerAddressModel? _selectedAddress; + private bool _loadingAddresses; + + private long _discountBalance; + private long _discountBalanceToUse; + private string? _notes; + private bool _placing; + + /// + /// Maximum discount allowed based on cart items' MaxDiscountPercent + /// + private long MaxAllowedDiscount => DiscountCart.TotalDiscount; + + /// + /// Maximum the user can actually use = min(balance, allowed discount) + /// + private long MaxUsable => Math.Min(_discountBalance, MaxAllowedDiscount); + + /// + /// Amount to be charged via payment gateway + /// + private long GatewayAmount => DiscountCart.TotalPrice - _discountBalanceToUse; + + private bool CanPlaceOrder => DiscountCart.Items.Count > 0 && _selectedAddress is not null; + + protected override async Task OnInitializedAsync() + { + await DiscountCart.EnsureInitializedAsync(); + + var addressTask = LoadAddresses(); + var balanceTask = LoadDiscountBalance(); + await Task.WhenAll(addressTask, balanceTask); + + // Default: use maximum possible discount + _discountBalanceToUse = MaxUsable; + } + + private async Task LoadDiscountBalance() + { + try + { + var balances = await WalletService.GetBalancesAsync(); + _discountBalance = balances.DiscountBalance; + } + catch + { + _discountBalance = 0; + } + } + + private async Task LoadAddresses() + { + _loadingAddresses = true; + try + { + var response = await UserAddressContract.GetCustomerAddressesAsync(new()); + if (response?.Models?.Any() == true) + { + _addresses = response.Models.ToList(); + _selectedAddress = _addresses.FirstOrDefault(a => a.IsDefault) ?? _addresses.First(); + } + else + { + _addresses = new(); + } + } + catch (Exception ex) + { + Snackbar.Add($"خطا در بارگذاری آدرس‌ها: {ex.Message}", Severity.Error); + _addresses = new(); + } + finally + { + _loadingAddresses = false; + await InvokeAsync(StateHasChanged); + } + } + + private async Task PlaceOrder() + { + if (!CanPlaceOrder || _selectedAddress is null) + { + Snackbar.Add("لطفاً آدرس را انتخاب کنید.", Severity.Warning); + return; + } + + _placing = true; + try + { + // Clamp the discount balance to use + var discountToUse = Math.Min(_discountBalanceToUse, MaxUsable); + + var result = await DiscountOrderService.PlaceOrderAsync( + _selectedAddress.Id, + discountToUse, + _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('/'); +} diff --git a/src/FrontOffice.Main/Pages/DiscountStore/OrderDetail.razor b/src/FrontOffice.Main/Pages/DiscountStore/OrderDetail.razor new file mode 100644 index 0000000..a00c0de --- /dev/null +++ b/src/FrontOffice.Main/Pages/DiscountStore/OrderDetail.razor @@ -0,0 +1,198 @@ +@attribute [Route("/discount-store/order/{Id:long}")] + +جزئیات سفارش تخفیفی + +@if (!string.IsNullOrEmpty(_paymentMessage)) +{ + + + @_paymentMessage + + +} + +@if (_loading) +{ + + + +} +else if (_order is null) +{ + + + +} +else +{ + + + + + + @(_order.PaymentCompleted ? "پرداخت شده" : "در انتظار پرداخت") + + + @DiscountOrderService.GetDeliveryStatusText(_order.DeliveryStatus) + + + + + + + تاریخ ثبت: @FormatDate(_order.Created) + + + @* ── Address ── *@ + @if (_order.Address is not null) + { + + آدرس: @_order.Address.Title — @_order.Address.Address + + @if (!string.IsNullOrWhiteSpace(_order.Address.PostalCode)) + { + کد پستی: @_order.Address.PostalCode + } + } + + + + @* ── Desktop: Table ── *@ + + اقلام سفارش + + + محصول + قیمت واحد + تعداد + سقف تخفیف + تخفیف + قیمت نهایی + + + @context.Title + @FormatPrice(context.UnitPrice) + @context.Count + + @if (context.MaxDiscountPercent > 0) + { + @context.MaxDiscountPercent% + } + + + @if (context.DiscountAmount > 0) + { + @FormatPrice(context.DiscountAmount)- + } + else + { + + } + + @FormatPrice(context.FinalPrice) + + + + + @* ── Mobile: Cards ── *@ + + اقلام سفارش + + @foreach (var item in _order.Items) + { + + + @item.Title + + قیمت واحد: + @FormatPrice(item.UnitPrice) + + + تعداد: + @item.Count + + @if (item.DiscountAmount > 0) + { + + تخفیف (@item.MaxDiscountPercent%): + @FormatPrice(item.DiscountAmount)- + + } + + قیمت نهایی: + @FormatPrice(item.FinalPrice) + + + + } + + + + + + @* ── Financial Summary ── *@ + + خلاصه مالی + + جمع کل: + @FormatPrice(_order.TotalPrice) + + + @if (_order.DiscountBalanceUsed > 0) + { + + از کیف تخفیفی: + @FormatPrice(_order.DiscountBalanceUsed)- + + } + + + + + پرداخت درگاه: + + @FormatPrice(_order.GatewayAmount) + + + + + @* ── Tracking / Transaction ── *@ + @if (!string.IsNullOrWhiteSpace(_order.TrackingCode)) + { + + کد رهگیری: + @_order.TrackingCode + + } + @if (!string.IsNullOrWhiteSpace(_order.TransactionId)) + { + + شناسه تراکنش: + @_order.TransactionId + + } + + @* ── Notes ── *@ + @if (!string.IsNullOrWhiteSpace(_order.Notes)) + { + + + توضیحات: @_order.Notes + + } + @if (!string.IsNullOrWhiteSpace(_order.AdminNotes)) + { + + پیام مدیر: @_order.AdminNotes + + } + + + +} diff --git a/src/FrontOffice.Main/Pages/DiscountStore/OrderDetail.razor.cs b/src/FrontOffice.Main/Pages/DiscountStore/OrderDetail.razor.cs new file mode 100644 index 0000000..4c57dac --- /dev/null +++ b/src/FrontOffice.Main/Pages/DiscountStore/OrderDetail.razor.cs @@ -0,0 +1,76 @@ +using Microsoft.AspNetCore.Components; +using DateTimeConverterCL; +using FrontOffice.Main.Utilities; + +namespace FrontOffice.Main.Pages.DiscountStore; + +public partial class OrderDetail +{ + [Parameter] public long Id { get; set; } + + [SupplyParameterFromQuery(Name = "payment")] + public string? PaymentResult { get; set; } + + [Inject] private DiscountOrderService DiscountOrderService { get; set; } = default!; + + private DiscountOrderDetail? _order; + private bool _loading = true; + private string? _paymentMessage; + private MudBlazor.Severity _paymentSeverity; + + protected override async Task OnInitializedAsync() + { + // نمایش پیام نتیجه پرداخت (بعد از بازگشت از درگاه) + if (!string.IsNullOrEmpty(PaymentResult)) + { + switch (PaymentResult.ToLower()) + { + case "success": + _paymentMessage = "پرداخت با موفقیت انجام شد! سفارش شما ثبت نهایی شد."; + _paymentSeverity = MudBlazor.Severity.Success; + break; + case "failed": + _paymentMessage = "پرداخت ناموفق بود. مبلغ رزرو شده آزاد خواهد شد."; + _paymentSeverity = MudBlazor.Severity.Error; + break; + case "error": + _paymentMessage = "خطایی در پردازش پرداخت رخ داد. لطفاً با پشتیبانی تماس بگیرید."; + _paymentSeverity = MudBlazor.Severity.Warning; + break; + } + } + + await LoadOrderAsync(); + } + + private async Task LoadOrderAsync() + { + _loading = true; + try + { + _order = await DiscountOrderService.GetOrderByIdAsync(Id); + } + catch + { + Snackbar.Add("خطا در بارگذاری سفارش", MudBlazor.Severity.Error); + } + finally + { + _loading = false; + } + } + + private static string FormatPrice(long price) => $"{price:N0} تومان"; + + private static string FormatDate(DateTime date) + { + try + { + return date.ToLocalTime().MiladiToJalaliWithTime(); + } + catch + { + return date.ToString("yyyy/MM/dd"); + } + } +} diff --git a/src/FrontOffice.Main/Pages/DiscountStore/Orders.razor b/src/FrontOffice.Main/Pages/DiscountStore/Orders.razor new file mode 100644 index 0000000..a91cb51 --- /dev/null +++ b/src/FrontOffice.Main/Pages/DiscountStore/Orders.razor @@ -0,0 +1,130 @@ +@attribute [Route(RouteConstants.DiscountStore.Orders)] + +سفارش‌های فروشگاه تخفیفی + + + + + + @if (_loading) + { + + } + else if (_orders.Count == 0) + { + هنوز سفارشی ثبت نشده است. + + مشاهده فروشگاه تخفیفی + + } + else + { + + + + + + شماره سفارش + تعداد اقلام + مبلغ کل + از کیف تخفیفی + درگاه + وضعیت پرداخت + وضعیت ارسال + تاریخ + + + + @context.OrderNumber + @context.ItemsCount + @FormatPrice(context.TotalPrice) + + @if (context.DiscountBalanceUsed > 0) + { + @FormatPrice(context.DiscountBalanceUsed) + } + else + { + + } + + @FormatPrice(context.GatewayAmount) + + + @(context.PaymentCompleted ? "پرداخت شده" : "در انتظار پرداخت") + + + + + @DiscountOrderService.GetDeliveryStatusText(context.DeliveryStatus) + + + @FormatDate(context.Created) + + + جزئیات + + + + + + + + + + + @foreach (var order in _orders) + { + + + + سفارش #@order.OrderNumber + + @(order.PaymentCompleted ? "پرداخت شده" : "در انتظار") + + + + @FormatDate(order.Created) + + @DiscountOrderService.GetDeliveryStatusText(order.DeliveryStatus) + + + + + @order.ItemsCount قلم + @FormatPrice(order.TotalPrice) تومان + + @if (order.DiscountBalanceUsed > 0) + { + + از کیف تخفیفی: @FormatPrice(order.DiscountBalanceUsed) تومان + + } + + + } + + + + @if (_totalPages > 1) + { + + + + } + } + + diff --git a/src/FrontOffice.Main/Pages/DiscountStore/Orders.razor.cs b/src/FrontOffice.Main/Pages/DiscountStore/Orders.razor.cs new file mode 100644 index 0000000..89c6e59 --- /dev/null +++ b/src/FrontOffice.Main/Pages/DiscountStore/Orders.razor.cs @@ -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 _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"); + } + } +} diff --git a/src/FrontOffice.Main/Pages/DiscountStore/ProductDetail.razor b/src/FrontOffice.Main/Pages/DiscountStore/ProductDetail.razor new file mode 100644 index 0000000..fcabe1d --- /dev/null +++ b/src/FrontOffice.Main/Pages/DiscountStore/ProductDetail.razor @@ -0,0 +1,140 @@ +@attribute [Route("/discount-store/product/{Id:long}")] + +@(_product?.Title ?? "محصول تخفیفی") + + + @if (_loading) + { + + } + else if (_product is null) + { + + } + else + { + + + + + + + + + + @if (_product.Images.Count > 1) + { + + @foreach (var img in _product.Images) + { + + + + } + + } + + + + + + + @_product.Title + + + @if (_product.Categories.Any()) + { + + @foreach (var cat in _product.Categories) + { + @cat.Title + } + + } + + + + + + قیمت محصول: + + @($"{_product.Price:N0}") تومان + + + @if (_product.MaxDiscountPercent > 0) + { + + سقف پرداخت از کیف تخفیفی: + + تا @_product.MaxDiscountPercent% (تا @($"{_product.Price * _product.MaxDiscountPercent / 100:N0}") تومان) + + + } + + + + + + + @(_product.RemainingCount > 0 ? $"موجود ({_product.RemainingCount} عدد)" : "ناموجود") + + + @_product.ViewCount بازدید + + + + + @if (!string.IsNullOrWhiteSpace(_product.ShortInfo)) + { + @_product.ShortInfo + } + + + + + + @(_addingToCart ? "در حال افزودن..." : "افزودن به سبد") + + + مشاهده سبد + + + + + + + + @if (!string.IsNullOrWhiteSpace(_product.FullInfo)) + { + + توضیحات محصول + +
+ @((MarkupString)_product.FullInfo) +
+
+ } + } +
diff --git a/src/FrontOffice.Main/Pages/DiscountStore/ProductDetail.razor.cs b/src/FrontOffice.Main/Pages/DiscountStore/ProductDetail.razor.cs new file mode 100644 index 0000000..fd90d66 --- /dev/null +++ b/src/FrontOffice.Main/Pages/DiscountStore/ProductDetail.razor.cs @@ -0,0 +1,64 @@ +using Microsoft.AspNetCore.Components; +using FrontOffice.Main.Utilities; + +namespace FrontOffice.Main.Pages.DiscountStore; + +public partial class ProductDetail +{ + [Parameter] public long Id { get; set; } + + [Inject] private DiscountProductService DiscountProductService { get; set; } = default!; + [Inject] private DiscountCartService DiscountCartService { get; set; } = default!; + + private DiscountProductDetail? _product; + private string _selectedImage = string.Empty; + private int _quantity = 1; + private bool _loading = true; + private bool _addingToCart; + + protected override async Task OnInitializedAsync() + { + await LoadProductAsync(); + } + + private async Task LoadProductAsync() + { + _loading = true; + try + { + _product = await DiscountProductService.GetByIdAsync(Id); + if (_product is not null) + { + _selectedImage = _product.Images.FirstOrDefault()?.ImageUrl + ?? _product.ThumbnailUrl; + } + } + catch + { + Snackbar.Add("خطا در بارگذاری محصول", MudBlazor.Severity.Error); + } + finally + { + _loading = false; + } + } + + private async Task AddToCart() + { + if (_product is null) return; + _addingToCart = true; + try + { + await DiscountCartService.AddAsync(_product.Id, _quantity); + Snackbar.Add($"{_product.Title} به سبد خرید اضافه شد", MudBlazor.Severity.Success); + } + catch + { + Snackbar.Add("خطا در افزودن به سبد خرید", MudBlazor.Severity.Error); + } + finally + { + _addingToCart = false; + } + } +} diff --git a/src/FrontOffice.Main/Pages/DiscountStore/Products.razor b/src/FrontOffice.Main/Pages/DiscountStore/Products.razor new file mode 100644 index 0000000..7312608 --- /dev/null +++ b/src/FrontOffice.Main/Pages/DiscountStore/Products.razor @@ -0,0 +1,133 @@ +@attribute [Route(RouteConstants.DiscountStore.Products)] + +فروشگاه تخفیفی + + + + + + + فروشگاه تخفیفی + + خرید با استفاده از موجودی کیف پول تخفیفی + + + + + + + + + + + + + @foreach (var cat in _categories) + { + @cat.Title (@cat.ProductCount) + } + + + + جستجو + + + + سبد خرید + @if (DiscountCart.Count > 0) + { + + } + + + + + + + @if (_loading) + { + + + در حال بارگذاری... + + } + else if (_result.Products.Count == 0) + { + محصولی یافت نشد. + } + else + { + + + @foreach (var p in _result.Products) + { + + + +
+ @if (p.RemainingCount <= 0) + { +
+ ناموجود +
+ } + else if (p.RemainingCount <= 5) + { + + فقط @p.RemainingCount عدد + + } + @if (p.MaxDiscountPercent > 0) + { + + تا @p.MaxDiscountPercent% تخفیف + + } +
+
+ @p.Title + @FormatPrice(p.Price) +
+
+ + + @(p.RemainingCount <= 0 ? "ناموجود" : "افزودن") + + +
+
+ } +
+ + + @if (_result.TotalPages > 1) + { +
+ +
+ } + } +
diff --git a/src/FrontOffice.Main/Pages/DiscountStore/Products.razor.cs b/src/FrontOffice.Main/Pages/DiscountStore/Products.razor.cs new file mode 100644 index 0000000..e84463c --- /dev/null +++ b/src/FrontOffice.Main/Pages/DiscountStore/Products.razor.cs @@ -0,0 +1,87 @@ +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Web; +using FrontOffice.Main.Utilities; + +namespace FrontOffice.Main.Pages.DiscountStore; + +public partial class Products : ComponentBase, IDisposable +{ + [Inject] private DiscountProductService ProductService { get; set; } = default!; + [Inject] private DiscountCartService DiscountCart { get; set; } = default!; + + private string _search = string.Empty; + private long? _selectedCategoryId; + private int _currentPage = 1; + private bool _loading; + private const int PageSize = 12; + + private DiscountProductListResult _result = new(new(), 0, 0, 1); + private List _categories = new(); + + protected override async Task OnInitializedAsync() + { + _loading = true; + await DiscountCart.EnsureInitializedAsync(); + DiscountCart.OnChange += StateHasChanged; + var categoriesTask = ProductService.GetCategoriesAsync(); + var productsTask = ProductService.GetProductsAsync(page: 1, pageSize: PageSize); + await Task.WhenAll(categoriesTask, productsTask); + _categories = categoriesTask.Result; + _result = productsTask.Result; + _loading = false; + } + + private async Task LoadProductsAsync() + { + _loading = true; + StateHasChanged(); + _result = await ProductService.GetProductsAsync( + page: _currentPage, + pageSize: PageSize, + search: string.IsNullOrWhiteSpace(_search) ? null : _search, + categoryId: _selectedCategoryId); + _loading = false; + StateHasChanged(); + } + + private async Task SearchProducts() => await LoadProductsAsync(); + + private async Task OnSearchKeyUp(KeyboardEventArgs e) + { + if (e.Key == "Enter") + { + _currentPage = 1; + await LoadProductsAsync(); + } + } + + private async Task OnCategoryChanged(long? value) + { + _selectedCategoryId = value; + _currentPage = 1; + await LoadProductsAsync(); + } + + private async Task OnPageChanged(int page) + { + _currentPage = page; + await LoadProductsAsync(); + } + + private async Task AddToCart(DiscountProductCard p) + { + await DiscountCart.AddAsync(p.Id); + } + + private void NavigateToProduct(long id) + { + Navigation.NavigateTo($"{RouteConstants.DiscountStore.ProductDetail}{id}"); + } + + private static string FormatPrice(long price) => $"{price:N0} تومان"; + + public void Dispose() + { + DiscountCart.OnChange -= StateHasChanged; + } +} diff --git a/src/FrontOffice.Main/Pages/FAQ.razor b/src/FrontOffice.Main/Pages/FAQ.razor index 1277aef..7c31375 100644 --- a/src/FrontOffice.Main/Pages/FAQ.razor +++ b/src/FrontOffice.Main/Pages/FAQ.razor @@ -116,7 +116,7 @@
- + سوال شما پاسخ داده نشد؟ diff --git a/src/FrontOffice.Main/Pages/Gateway/CartChooser.razor b/src/FrontOffice.Main/Pages/Gateway/CartChooser.razor new file mode 100644 index 0000000..c1e57d1 --- /dev/null +++ b/src/FrontOffice.Main/Pages/Gateway/CartChooser.razor @@ -0,0 +1,62 @@ +@attribute [Route(RouteConstants.Gateway.CartChooser)] + +سبد خرید + + + + + + + سبد خرید کدام فروشگاه را می‌خواهید مشاهده کنید؟ + + + + + + + + + + سبد خرید فروشگاه + @if (_cartCount > 0) + { + + @_cartCount محصول + + } + else + { + سبد خالی + } + + + + + + + + + + + سبد خرید تخفیفی + @if (_discountCartCount > 0) + { + + @_discountCartCount محصول + + } + else + { + سبد خالی + } + + + + + + + بازگشت به داشبورد + + + diff --git a/src/FrontOffice.Main/Pages/Gateway/CartChooser.razor.cs b/src/FrontOffice.Main/Pages/Gateway/CartChooser.razor.cs new file mode 100644 index 0000000..1c9b990 --- /dev/null +++ b/src/FrontOffice.Main/Pages/Gateway/CartChooser.razor.cs @@ -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"); +} diff --git a/src/FrontOffice.Main/Pages/Gateway/OrdersChooser.razor b/src/FrontOffice.Main/Pages/Gateway/OrdersChooser.razor new file mode 100644 index 0000000..58c531b --- /dev/null +++ b/src/FrontOffice.Main/Pages/Gateway/OrdersChooser.razor @@ -0,0 +1,49 @@ +@attribute [Route(RouteConstants.Gateway.OrdersChooser)] +@inject IJSRuntime JS + +سفارشات من + + + + + + + سفارشات کدام فروشگاه را می‌خواهید مشاهده کنید؟ + + + + + + + + + + سفارشات فروشگاه + سفارشات خرید معمولی + + + + + + + + + + + سفارشات فروشگاه تخفیفی + سفارشات خرید تخفیفی + + + + + + + بازگشت به داشبورد + + + + +@code { + private async Task GoBack() => await JS.InvokeVoidAsync("history.back"); +} diff --git a/src/FrontOffice.Main/Pages/Gateway/StoreChooser.razor b/src/FrontOffice.Main/Pages/Gateway/StoreChooser.razor new file mode 100644 index 0000000..30becda --- /dev/null +++ b/src/FrontOffice.Main/Pages/Gateway/StoreChooser.razor @@ -0,0 +1,56 @@ +@attribute [Route(RouteConstants.Gateway.StoreChooser)] + +فروشگاه‌ها + + + + + + برای خرید، یکی از فروشگاه‌ها را انتخاب کنید. هر فروشگاه سبد خرید و سفارشات مجزای خود را دارد. + + + + + + + + + + + فروشگاه + + خرید محصولات با پرداخت از کیف پول اعتباری + + @if (_cartCount > 0) + { + + @_cartCount محصول در سبد خرید + + } + + + + + + + + + + + + فروشگاه تخفیفی + + خرید با استفاده از موجودی کیف تخفیفی + درگاه پرداخت + + @if (_discountCartCount > 0) + { + + @_discountCartCount محصول در سبد خرید + + } + + + + + + diff --git a/src/FrontOffice.Main/Pages/Gateway/StoreChooser.razor.cs b/src/FrontOffice.Main/Pages/Gateway/StoreChooser.razor.cs new file mode 100644 index 0000000..e19a095 --- /dev/null +++ b/src/FrontOffice.Main/Pages/Gateway/StoreChooser.razor.cs @@ -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; + } +} diff --git a/src/FrontOffice.Main/Pages/Index.razor b/src/FrontOffice.Main/Pages/Index.razor index 0da1b8c..2be5568 100644 --- a/src/FrontOffice.Main/Pages/Index.razor +++ b/src/FrontOffice.Main/Pages/Index.razor @@ -1,354 +1,343 @@ @attribute [Route(RouteConstants.Main.MainPage)] -@inject NavigationManager Navigation +@inject IJSRuntime JS -صفحه اصلی +صفحه اصلی | کارا بازار سلامت - -
- - - - پیشنهاد ویژه برای شروع - - - با دعوت از دوستان خود، از خریدهای واقعی پاداش بگیرید - - - ثبت‌نام آسان، تجربهٔ سریع و شفاف. همین امروز حساب بساز و با دعوت دوستانت، امتیاز و پاداش دریافت کن. - +@* ═══════════════════════════════════════════════ + 1. HERO — Bold, minimal, centered + ═══════════════════════════════════════════════ *@ +
+ + + + 🌿 پلتفرم سلامت‌محور فروش و تیم‌سازی + - - مشاهده قیمت‌ها - شروع کن - - - - ثبت‌نام زیر ۲ دقیقه - پشتیبانی ۷×۲۴ - پرداخت ایمن + + رشد تیم، فروش واقعی، پاداش شفاف + + + + با دعوت از دوستان، از خریدهای واقعی محصولات سلامت پاداش بگیرید. ثبت‌نام سریع، داشبورد لحظه‌ای. + + + + + شروع رایگان + + + آخرین اخبار + + + + @* ── Trust badges ── *@ + + @foreach (var badge in _trustBadges) + { + + + @badge.Text + } + + + +
+@* ═══════════════════════════════════════════════ + 1b. FEATURED POST BANNER — below hero + ═══════════════════════════════════════════════ *@ +@if (_latestPosts.Any()) +{ + var featured = _latestPosts.First(); +
+ + + + @if (!string.IsNullOrWhiteSpace(featured.ThumbnailUrl)) + { + + } + else + { + + + + } + + + 🔥 جدیدترین مطلب + + @featured.Title + @if (!string.IsNullOrWhiteSpace(featured.Summary)) + { + @featured.Summary + } + + - + + +
+} - - - - - +@* ═══════════════════════════════════════════════ + 2. HOW IT WORKS — 3 numbered steps + ═══════════════════════════════════════════════ *@ +
+ +
+ سه گام تا شروع + + بدون پیچیدگی، سریع و آسان + +
+ + + @foreach (var (step, i) in _steps.Select((s, i) => (s, i))) + { + + + + @(i + 1) + + @step.Title + @step.Desc + + + }
- - -
- - - - چرا کاربران ما را انتخاب می‌کنند؟ - - - سه دلیل روشن برای شروع امروز. +@* ═══════════════════════════════════════════════ + 3. FEATURES — 6 cards with icons + ═══════════════════════════════════════════════ *@ +
+ +
+ چرا کارا بازار سلامت؟ + + ابزارهایی ساده و قدرتمند برای رشد کسب‌وکار شما +
- - - - - - - - ثبت‌نام سریع و ساده - - - - در چند گام کوتاه، حسابت را بساز و شروع کن. - - - - - - - - - - - مزایا و پاداش‌های شفاف - - - - قوانین روشن، دسترسی آسان به سوابق و گزارش‌ها. - - - - - - - - - - - اپلیکیشن واکنش‌گرا - - - - تجربه‌ای روان در موبایل و دسکتاپ. - - - - - - -
-
- - @* -
- - - پکیج‌های سرمایه گذاری - - - بر اساس اندازهٔ تیم خود انتخاب کنید. - - @if (_isLoadingPackages) + + @foreach (var (icon, title, desc, delay) in _features) { - - - در حال بارگذاری پکیج‌ها... - + + + + + + + @title + @desc + + + } - else if (_packs.Any()) + + +
+ +@* ═══════════════════════════════════════════════ + 4. STATS — Counter cards + ═══════════════════════════════════════════════ *@ +
+ + + @foreach (var stat in _stats) { - - @foreach (var p in _packs) - { - - - - - @(p.Title) - @((MarkupString)p.Body) - - - @(p.Price.ToThousands().ToCurrencyUnitIRT()) + + + @stat.Display + @stat.Label + + + } + + +
+ +@* ═══════════════════════════════════════════════ + 5. LATEST BLOG POSTS + ═══════════════════════════════════════════════ *@ +@if (_latestPosts.Any()) +{ +
+ +
+ آخرین مقالات + + جدیدترین مطالب و اخبار کارا بازار سلامت + +
+ + + @foreach (var post in _latestPosts.Take(2)) + { + + +
+ @if (!string.IsNullOrWhiteSpace(post.ThumbnailUrl)) + { + + } + else + { +
+ +
+ } +
+
+ @if (post.Categories.Any()) + { + + @post.Categories.First().Title + + } + + @post.Title + + @if (!string.IsNullOrWhiteSpace(post.Summary)) + { + + @post.Summary + + } + + @if (post.PublishedAt.HasValue) + { + + + @post.PublishedAt.Value.ToString("yyyy/MM/dd") + + } - مشاهده جزئیات - - - - } - - } - else - { - - هیچ پکیجی یافت نشد. - - } - -
- *@ - -
- - - کارا بازار سلامت چطور کار می‌کند؟ - - - سه گام روشن تا شروع یک ماجراجویی جدید - - - - - - - - - ثبت‌نام - - - مرحله اول - یک حساب بساز و وارد شو. - - - - - - دعوت دوستان - - - مرحله دوم - لینک دعوتت را به اشتراک بگذار. - - - - - - دریافت پاداش - - - مرحله سوم - از خریدهای واقعی دوستانت پاداش بگیر. - - - + + + @post.ViewCount + + +
-
-
- - - - - - - - - +۵۰٪ - رشد میانگین تیم - - - - - ۹۹٫۹٪ - آپ‌تایم سرویس - - - - - ۳ روز - میانگین زمان استقرار - - - - - +۲۰ کشور - پوشش ارسال کد - - - - - - +
+ }
-
-
- - -
- - - اعتماد مشتریان - - - بخشی از تجربهٔ استفاده از «کارا بازار سلامت». - - - - - - - - - -
- شرکت سینا نت - مدیر عملیات -
-
- - «با کارا بازار سلامت، محاسبهٔ کارمزدها و پایش تیم‌ها بدون اکسل و دردسر انجام می‌شود.» - -
-
-
- - - - - - - - -
- هولدینگ آریانا - مدیر فروش -
-
- - «شجره‌نامهٔ بصری و گزارش‌های دقیق باعث شد رشد تیم را لحظه‌ای ببینیم.» - -
-
-
- -
-
-
- - -
- - - سوالات متداول - - - پاسخ به سوالات رایج شما. - -
- - @foreach (var q in _faqs) - { - - @q.A - - } - +
+ + مشاهده همه مقالات +
+} -
+@* ═══════════════════════════════════════════════ + 6. TESTIMONIALS + ═══════════════════════════════════════════════ *@ +
+ +
+ اعتماد مشتریان + + بخشی از تجربه استفاده از «کارا بازار سلامت» + +
+ + + @foreach (var t in _testimonials) + { + + + + + + «@t.Quote» + + + + + + +
+ @t.Name + @t.Role +
+
+
+
+
+ } +
+
+
+ +@* ═══════════════════════════════════════════════ + 7. FAQ + ═══════════════════════════════════════════════ *@ +
+ +
+ سوالات متداول + + پاسخ به سوالات رایج شما + +
+ +
+ + @foreach (var q in _faqs) + { + + @q.A + + } + +
+
+
+ +@* ═══════════════════════════════════════════════ + 8. CTA BANNER + ═══════════════════════════════════════════════ *@ +
+ +
+ + + آماده شروع هستید؟ + + + همین الان ثبت‌نام کنید و از مزایای کارا بازار سلامت بهره‌مند شوید. + + + شروع رایگان + + +
+
+
diff --git a/src/FrontOffice.Main/Pages/Index.razor.cs b/src/FrontOffice.Main/Pages/Index.razor.cs index bd4a508..595ff44 100644 --- a/src/FrontOffice.Main/Pages/Index.razor.cs +++ b/src/FrontOffice.Main/Pages/Index.razor.cs @@ -1,44 +1,66 @@ -using CMSMicroservice.Protobuf.Protos.Package; -using FrontOffice.Main.Utilities; +using FrontOffice.Main.Utilities; using Microsoft.AspNetCore.Components; +using Microsoft.JSInterop; using MudBlazor; -using System.Security.Cryptography; namespace FrontOffice.Main.Pages; -public partial class Index:IDisposable +public partial class Index : IDisposable { - [Inject] private PackageContract.PackageContractClient PackageClient { get; set; } = default!; - [Inject] private MobileNumberEncryptor Encryptor { get; set; } - private string? _email; - private bool _isLoadingPackages; - private List _packs = new(); + [Inject] private BlogPostService BlogPostService { get; set; } = default!; [Inject] private AuthService AuthService { get; set; } = default!; + // ── Latest blog posts (loaded from CMS) ── + private List _latestPosts = new(); + protected override async Task OnInitializedAsync() { - MainService.OnChangeHandler +=async () => + MainService.OnChangeHandler += OnStateChanged; + + // Load latest published blog posts + // اول پین‌شده‌ها، اگر کافی نبود آخرین پست‌ها اضافه شود + try { - await InvokeAsync(StateHasChanged); - }; - // await LoadPackagesAsync(); - - //string mobileNumber = "09387342688"; - - //// انکریپت کردن - //string encrypted = Encryptor.EncryptMobileNumber(mobileNumber); - //Console.WriteLine($"Encrypted: {encrypted}"); - - //// دیکریپت کردن برای تست - //string decrypted = Encryptor.DecryptMobileNumber(encrypted); - //Console.WriteLine($"Decrypted: {decrypted}"); + _latestPosts = await BlogPostService.GetFeaturedPostsAsync(2); + + // اگر فقط ۱ پین‌شده داریم، ۱ پست آخر هم اضافه کن + if (_latestPosts.Count < 2) + { + var result = await BlogPostService.GetPublishedPostsAsync(page: 1, pageSize: 2); + var existing = _latestPosts.Select(p => p.Id).ToHashSet(); + foreach (var post in result.Posts) + { + if (!existing.Contains(post.Id)) + { + _latestPosts.Add(post); + if (_latestPosts.Count >= 2) break; + } + } + } + } + catch + { + _latestPosts = new(); + } } protected override async Task OnAfterRenderAsync(bool firstRender) { + if (firstRender) + { + // Init scroll-triggered fade-in animations + await JS.InvokeVoidAsync("initScrollAnimations"); + + // Animate stat counters + foreach (var stat in _stats) + { + await JS.InvokeVoidAsync("animateCounter", stat.ElementId, stat.Target, 2200, stat.Suffix); + } + } + if (await AuthService.IsAuthenticatedAsync()) { - if ((await AuthService.IsCompleteRegisterAsync())) + if (await AuthService.IsCompleteRegisterAsync()) { Navigation.NavigateTo(RouteConstants.Profile.Index); } @@ -47,56 +69,8 @@ public partial class Index:IDisposable Navigation.NavigateTo(RouteConstants.Registration.Wizard); } } - await base.OnAfterRenderAsync(firstRender); - } - // private async Task LoadPackagesAsync() - // { - // _isLoadingPackages = true; - // try - // { - // var response = await PackageClient.GetAllPackageByFilterAsync(request: new()); - // if (response?.Models?.Any() == true) - // { - // _packs = response.Models.Select(p => new Pack( - // Id: p.Id, - // Title: p.Title, - // Body: p.Description, - // Image: UrlUtility.DownloadUrl + p.ImagePath, - // Price: p.Price - // )).ToList(); - // } - // else - // _packs = new List(); - // } - // catch (Exception ex) - // { - // Snackbar.Add($"خطا در بارگذاری پکیج‌ها: {ex.Message}", Severity.Error); - // // Fallback to empty list - // _packs = new List(); - // } - // finally - // { - // _isLoadingPackages = false; - // await InvokeAsync(StateHasChanged); - // } - // } - - private void JoinWaitlist() - { - if (string.IsNullOrWhiteSpace(_email)) - { - Snackbar.Add("لطفاً ایمیل معتبر وارد کنید.", Severity.Warning); - return; - } - - Snackbar.Add("به لیست انتظار «کارا بازار سلامت» اضافه شدید.", Severity.Success); - _email = string.Empty; - } - - private void NavigateToPackage(long packageId) - { - Navigation.NavigateTo($"{RouteConstants.Package.Detail}{packageId}"); + await base.OnAfterRenderAsync(firstRender); } private void NavigateToRegistrationWizard() @@ -104,25 +78,77 @@ public partial class Index:IDisposable Navigation.NavigateTo(RouteConstants.Registration.Wizard); } - private record Pack(long Id, string Title, string Body, string Image, long Price); + private void NavigateToPost(string slug) + { + Navigation.NavigateTo($"/blog/{slug}"); + } - private record Plan(string Name, string Price, bool Highlight, IEnumerable Features); + private async void OnStateChanged() + { + await InvokeAsync(StateHasChanged); + } - private record QA(string Q, string A); + // ── Trust badges ── + private readonly List<(string Icon, string Text)> _trustBadges = new() + { + (Icons.Material.Outlined.Timer, "ثبت‌نام زیر ۲ دقیقه"), + (Icons.Material.Outlined.SupportAgent, "پشتیبانی ۷×۲۴"), + (Icons.Material.Outlined.Lock, "پرداخت ایمن"), + (Icons.Material.Outlined.Verified, "تضمین کیفیت"), + }; + // ── How it works steps ── + private readonly List<(string Title, string Desc)> _steps = new() + { + ("ثبت‌نام و احراز هویت", "یک حساب بسازید، شماره موبایل را تأیید و اطلاعات هویتی را تکمیل کنید."), + ("دعوت دوستان", "لینک دعوت اختصاصی خود را با دوستان و آشنایان به اشتراک بگذارید."), + ("دریافت پاداش", "از خریدهای واقعی اعضای تیمتان پاداش شفاف و لحظه‌ای دریافت کنید."), + }; + + // ── Feature cards data (6 cards) ── + private readonly List<(string Icon, string Title, string Desc, int Delay)> _features = new() + { + (Icons.Material.Outlined.VerifiedUser, "ثبت‌نام سریع و ساده", "در چند گام کوتاه حساب بسازید و شروع کنید.", 0), + (Icons.Material.Outlined.StarBorder, "پاداش‌های شفاف", "قوانین روشن، دسترسی آسان به سوابق و گزارش‌ها.", 100), + (Icons.Material.Outlined.Devices, "طراحی واکنش‌گرا", "تجربه‌ای روان در موبایل و دسکتاپ.", 200), + (Icons.Material.Outlined.Groups, "تیم‌سازی هوشمند", "ساختار درختی شبکه و مدیریت تیم‌های فروش.", 300), + (Icons.Material.Outlined.Insights, "گزارش‌های لحظه‌ای", "داشبورد پویا برای مشاهده عملکرد و کمیسیون.", 400), + (Icons.Material.Outlined.Lock, "امنیت بالا", "رمزنگاری اطلاعات و احراز هویت چندمرحله‌ای.", 500), + }; + + // ── Stats data (animated counters) ── + private readonly List _stats = new() + { + new("stat-growth", "+۵۰٪", 50, "%+", Color.Success, "رشد میانگین تیم"), + new("stat-uptime", "۹۹.۹٪", 99.9, "%", Color.Primary, "آپ‌تایم سرویس"), + new("stat-deploy", "۳ روز", 3, " روز", Color.Warning, "میانگین زمان استقرار"), + new("stat-coverage", "+۲۰ کشور", 20, "+", Color.Secondary, "پوشش ارسال کد"), + }; + + // ── Testimonials ── + private readonly List _testimonials = new() + { + new("با کارا بازار سلامت، محاسبه کارمزدها و پایش تیم‌ها بدون اکسل و دردسر انجام می‌شود.", "شرکت سینا نت", "مدیر عملیات", 0), + new("شجره‌نامه بصری و گزارش‌های دقیق باعث شد رشد تیم را لحظه‌ای ببینیم.", "هولدینگ آریانا", "مدیر فروش", 150), + new("سادگی ثبت‌نام و شفافیت پاداش‌ها مهم‌ترین مزیت این پلتفرم است.", "گروه بهداشتی نوین", "مدیر توسعه", 300), + }; + + // ── FAQ data ── private readonly List _faqs = new() { - new("دامنهٔ اختصاصی دارم؛ قابل اتصال است؟", "بله، پشت دامنه و گواهی SSL خودتان مستقر می‌شود."), + new("دامنه اختصاصی دارم؛ قابل اتصال است؟", "بله، پشت دامنه و گواهی SSL خودتان مستقر می‌شود."), new("با دیتابیس خودم کار می‌کند؟", "کاملاً. SQL Server، PostgreSQL و MySQL پشتیبانی می‌شود."), new("چه درگاه‌هایی پشتیبانی می‌شود؟", "Stripe و PayPal یا درگاه اختصاصی از طریق وب‌هوک‌ها."), new("می‌توانم داده‌ها را خروجی بگیرم؟", "هر زمان از داشبورد ادمین خروجی CSV/Excel بگیرید."), }; + // ── Records ── + private record QA(string Q, string A); + private record StatItem(string ElementId, string Display, double Target, string Suffix, Color Color, string Label); + private record TestimonialItem(string Quote, string Name, string Role, int Delay); + public void Dispose() { - MainService.OnChangeHandler -=async () => - { - await InvokeAsync(StateHasChanged); - }; + MainService.OnChangeHandler -= OnStateChanged; } } \ No newline at end of file diff --git a/src/FrontOffice.Main/Pages/Network/NetworkStatisticsPage.razor b/src/FrontOffice.Main/Pages/Network/NetworkStatisticsPage.razor index 2b2f254..95f7384 100644 --- a/src/FrontOffice.Main/Pages/Network/NetworkStatisticsPage.razor +++ b/src/FrontOffice.Main/Pages/Network/NetworkStatisticsPage.razor @@ -6,14 +6,11 @@ - - آمار باشگاه توسعه دهندگان - بازگشت - + @if (_isLoading) { - + } else if (_statistics is not null) { diff --git a/src/FrontOffice.Main/Pages/Package/MyPackages.razor b/src/FrontOffice.Main/Pages/Package/MyPackages.razor index 6eda2aa..95a2685 100644 --- a/src/FrontOffice.Main/Pages/Package/MyPackages.razor +++ b/src/FrontOffice.Main/Pages/Package/MyPackages.razor @@ -3,16 +3,7 @@ پکیج‌های من - - - - - - پکیج‌های من - - وضعیت پکیج‌های خریداری شده و عضویت باشگاه مشتریان - - + @if (_isLoading) { diff --git a/src/FrontOffice.Main/Pages/Package/Packages.razor b/src/FrontOffice.Main/Pages/Package/Packages.razor index 05c42dc..c7fb39c 100644 --- a/src/FrontOffice.Main/Pages/Package/Packages.razor +++ b/src/FrontOffice.Main/Pages/Package/Packages.razor @@ -3,13 +3,7 @@ پکیج‌ها - - - پکیج‌های سرمایه‌گذاری - - با خرید پکیج طلایی، به باشگاه مشتریان بپیوندید و از مزایای ویژه بهره‌مند شوید. - - + @if (_isLoading) { diff --git a/src/FrontOffice.Main/Pages/PackageDetail.razor b/src/FrontOffice.Main/Pages/PackageDetail.razor index 80f7a4e..b8b31b6 100644 --- a/src/FrontOffice.Main/Pages/PackageDetail.razor +++ b/src/FrontOffice.Main/Pages/PackageDetail.razor @@ -4,26 +4,25 @@ @if (_isLoading) { - - - در حال بارگذاری... - + + + } else if (_package == null) { - - - پکیج یافت نشد - پکیج مورد نظر وجود ندارد یا حذف شده است. - - بازگشت به صفحه اصلی - - + + + } else { + @@ -36,9 +35,9 @@ else - - @relatedPackage.Title diff --git a/src/FrontOffice.Main/Pages/PackageDetail.razor.cs b/src/FrontOffice.Main/Pages/PackageDetail.razor.cs index d73c758..2ea07bf 100644 --- a/src/FrontOffice.Main/Pages/PackageDetail.razor.cs +++ b/src/FrontOffice.Main/Pages/PackageDetail.razor.cs @@ -74,7 +74,7 @@ public partial class PackageDetail : IDisposable Id = packageResponse.Id, Title = packageResponse.Title, Body = packageResponse.Description, - Image = UrlUtility.DownloadUrl + packageResponse.ImagePath, + Image = packageResponse.ImagePath ?? string.Empty, Specifications = new List { new() { Name = "ظرفیت", Value = "تا ۲۰۰ عضو", Icon = Icons.Material.Filled.Group }, diff --git a/src/FrontOffice.Main/Pages/Profile/Addresses.razor b/src/FrontOffice.Main/Pages/Profile/Addresses.razor index 5748acc..4131798 100644 --- a/src/FrontOffice.Main/Pages/Profile/Addresses.razor +++ b/src/FrontOffice.Main/Pages/Profile/Addresses.razor @@ -4,10 +4,7 @@ - - مدیریت آدرس‌ها - بازگشت - + diff --git a/src/FrontOffice.Main/Pages/Profile/ChangePassword.razor b/src/FrontOffice.Main/Pages/Profile/ChangePassword.razor index da5c583..f453fbc 100644 --- a/src/FrontOffice.Main/Pages/Profile/ChangePassword.razor +++ b/src/FrontOffice.Main/Pages/Profile/ChangePassword.razor @@ -4,10 +4,7 @@ - - تغییر رمز عبور - بازگشت - + diff --git a/src/FrontOffice.Main/Pages/Profile/Components/OrganizationChart.razor b/src/FrontOffice.Main/Pages/Profile/Components/OrganizationChart.razor index 4554ebb..8b563fa 100644 --- a/src/FrontOffice.Main/Pages/Profile/Components/OrganizationChart.razor +++ b/src/FrontOffice.Main/Pages/Profile/Components/OrganizationChart.razor @@ -7,12 +7,12 @@ - 2 سطح - 3 سطح - 4 سطح - 5 سطح - 6 سطح - همه + 2 سطح + 3 سطح + 4 سطح + 5 سطح + 6 سطح + همه diff --git a/src/FrontOffice.Main/Pages/Profile/Components/OrganizationChartLevel.razor b/src/FrontOffice.Main/Pages/Profile/Components/OrganizationChartLevel.razor index fc1cf30..e9d4a2d 100644 --- a/src/FrontOffice.Main/Pages/Profile/Components/OrganizationChartLevel.razor +++ b/src/FrontOffice.Main/Pages/Profile/Components/OrganizationChartLevel.razor @@ -9,9 +9,9 @@ @if (!string.IsNullOrWhiteSpace(node.Avatar)) { - + } else diff --git a/src/FrontOffice.Main/Pages/Profile/Hub.razor b/src/FrontOffice.Main/Pages/Profile/Hub.razor new file mode 100644 index 0000000..83f24db --- /dev/null +++ b/src/FrontOffice.Main/Pages/Profile/Hub.razor @@ -0,0 +1,64 @@ +@attribute [Route(RouteConstants.Profile.Hub)] + +پروفایل + + + + + + + + + @($"{_userProfile.FirstName} {_userProfile.LastName}") + + @(_userProfile.Mobile) + + + + + + + + + + + اطلاعات شخصی + نام، کد ملی و تاریخ تولد + + + + + + + + + + + + + + آدرس‌ها + مدیریت آدرس‌های تحویل + + + + + + + + + + + + + + تنظیمات + تغییر رمز و تنظیمات حساب + + + + + + + + diff --git a/src/FrontOffice.Main/Pages/Profile/Hub.razor.cs b/src/FrontOffice.Main/Pages/Profile/Hub.razor.cs new file mode 100644 index 0000000..ab37be3 --- /dev/null +++ b/src/FrontOffice.Main/Pages/Profile/Hub.razor.cs @@ -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(); + } +} diff --git a/src/FrontOffice.Main/Pages/Profile/Index.razor b/src/FrontOffice.Main/Pages/Profile/Index.razor index 86e2f93..0a0761b 100644 --- a/src/FrontOffice.Main/Pages/Profile/Index.razor +++ b/src/FrontOffice.Main/Pages/Profile/Index.razor @@ -5,299 +5,237 @@ - + + @* ═══════════════════════════════════════════════ + 1. PROFILE HEADER — Modern gradient card + ═══════════════════════════════════════════════ *@ - - - - - - - -
- @($"{_userProfile.FirstName} {_userProfile.LastName}") - @(_userProfile.Mobile) - عضو از @(_userProfile.MobileVerifiedAt?.ToDateTime().MiladiToJalali()) -
-
- - - سامانه دایا + + + + + + + + @($"{_userProfile.FirstName} {_userProfile.LastName}") + + + @(_userProfile.Mobile) + + + عضو از @(_userProfile.MobileVerifiedAt?.ToDateTime().MiladiToJalali()) + - - - - - - @if (CanShowReferralLink) - { - - - کد دعوت شما - - - اشتراک‌گذاری - - - - این کد را با دوستان خود به اشتراک بگذارید تا از خریدهای آنها پاداش دریافت کنید. - - - - - - - @if (!string.IsNullOrWhiteSpace(_copyMessage)) - { - @(_copyMessage) - } - - } - else - { - - - - - لینک دعوت شما هنوز فعال نشده است - - - برای فعال‌سازی لینک دعوت و دعوت دوستان خود به جمع مشتریان ویژه کارابازار، کافی است ابتدا پکیج پایه ۵۶ میلیونی را تهیه کنید و سپس در باشگاه مشتریان عضو شوید. - - - از مزایای ویژه عضویت در باشگاه مشتریان کارابازار سلامت بهره‌مند شده و با فعالیت در زمینه توسعه فروشگاه‌ها پاداش دریافت کنید. - - - شروع فرآیند تامین اعتبار - - - - } + + سامانه دایا +
- + @* ═══════════════════════════════════════════════ + 2. WALLET STRIP — Compact inline stats + ═══════════════════════════════════════════════ *@ - - - - - موجودی اعتباری - @_walletCredit - - - - - موجودی تخفیف باشگاه - @_walletDiscount - - - - - موجودی پاداش تیمی - @_walletNetwork - - - - جزئیات کیف پول - - + + +
+
+ + اعتباری +
+ @_walletCredit +
+ +
+
+ + تخفیف باشگاه +
+ @_walletDiscount +
+ +
+
+ + پاداش تیمی +
+ @_walletNetwork +
+ + + کیف پول + +
- + @* ═══════════════════════════════════════════════ + 3. REFERRAL SECTION + ═══════════════════════════════════════════════ *@ - - - - - - - - اطلاعات شخصی - نمایش و ویرایش اطلاعات - - - - - - - - - - آدرس‌ها - مدیریت آدرس‌های شما - - - - - - - - - - شجره‌نامه - ساختار تیم - - - - - - - - - - تنظیمات حساب - اعلان‌ها و تنظیمات - - - - - - - - - - فروشگاه - مشاهده و خرید محصولات - - - - - - - - - - کیف پول - مدیریت و تاریخچه - - - - - - - - - - درخواست برداشت - ثبت و پیگیری برداشت - - - - - - - - - - باشگاه مشتریان - عضویت و مزایا - - - - - - - - - - آمار باشگاه - آمار جزئیات باشگاه - - - - - - - - - - پاداش - درآمد و تاریخچه - - - - - - + @if (CanShowReferralLink) + { + + + + کد دعوت شما + + + اشتراک‌گذاری + + + + این کد را با دوستان خود به اشتراک بگذارید تا از خریدهای آنها پاداش دریافت کنید. + + + @if (!string.IsNullOrWhiteSpace(_copyMessage)) + { + @(_copyMessage) + } + + } + else + { + + + + لینک دعوت شما هنوز فعال نشده است + + + برای فعال‌سازی لینک دعوت، ابتدا پکیج پایه را تهیه کنید و سپس در باشگاه مشتریان عضو شوید. + + + از مزایای ویژه عضویت بهره‌مند شده و با فعالیت در زمینه توسعه فروشگاه‌ها پاداش دریافت کنید. + + + شروع فرآیند تامین اعتبار + + + } + + @* ═══════════════════════════════════════════════ + 3.5 LATEST BLOG BANNER + ═══════════════════════════════════════════════ *@ + @if (_latestPost is not null) + { + + + + + @if (!string.IsNullOrWhiteSpace(_latestPost.ThumbnailUrl)) + { +
+ +
+ } + else + { + + + + } + + جدیدترین مطلب + @_latestPost.Title + + +
+
+
+
+ } + + @* ═══════════════════════════════════════════════ + 4. QUICK ACCESS TILES — Modern grid + ═══════════════════════════════════════════════ *@ + + دسترسی سریع + + @foreach (var tile in _dashTiles) + { + + + + + + + @tile.Title + @tile.Subtitle + + + + } + + +
- +@* ── Purchase Options Dialog ── *@ خرید پکیج پایه ۵۶ میلیون تومان - - برای خرید پکیج پایه، یکی از روش‌های زیر را انتخاب کنید: - پرداخت مستقیم - - پرداخت آنی از طریق درگاه بانکی - - + پرداخت آنی از طریق درگاه بانکی + پرداخت با کارت بانکی(بزودی) - اعتبار الماسی دایا - - تأمین اعتبار از طریق خرید توکن الماس و دریافت وام - - + تأمین اعتبار از طریق خرید توکن الماس و دریافت وام + تأمین اعتبار الماسی - + بستن diff --git a/src/FrontOffice.Main/Pages/Profile/Index.razor.cs b/src/FrontOffice.Main/Pages/Profile/Index.razor.cs index 6282e3c..68f8c5e 100644 --- a/src/FrontOffice.Main/Pages/Profile/Index.razor.cs +++ b/src/FrontOffice.Main/Pages/Profile/Index.razor.cs @@ -22,10 +22,11 @@ public partial class Index [Inject] private UserAddressContract.UserAddressContractClient UserAddressContract { get; set; } = default!; [Inject] private PackageContract.PackageContractClient PackageContract { get; set; } = default!; [Inject] private AuthService AuthService { get; set; } = default!; + [Inject] private BlogPostService BlogPostService { get; set; } = default!; - private GetUserResponse _userProfile = new(); - private UpdateUserRequest _updateUserRequest = new(); - private readonly UpdateUserRequestValidator _personalValidator = new(); + private GetUserForCustomerResponse _userProfile = new(); + private BlogPostCardDto? _latestPost; + private UpdateCustomerProfileRequest _updateUserRequest = new(); private MudForm? _personalForm; @@ -45,6 +46,22 @@ public partial class Index private bool _showPurchaseBottomSheet; private bool _isProcessingPayment; + // Dashboard Tiles + private readonly List _dashTiles = new() + { + new(RouteConstants.Profile.Personal, Icons.Material.Filled.Person, "اطلاعات شخصی", "نمایش و ویرایش", "background:rgba(99,102,241,.12); color:#6366f1;"), + new(RouteConstants.Profile.Addresses, Icons.Material.Filled.LocationOn, "آدرس‌ها", "مدیریت آدرس‌ها", "background:rgba(99,102,241,.12); color:#6366f1;"), + new(RouteConstants.Profile.Tree, Icons.Material.Filled.AccountTree, "شجره‌نامه", "ساختار تیم", "background:rgba(99,102,241,.12); color:#6366f1;"), + new(RouteConstants.Gateway.StoreChooser, Icons.Material.Filled.Store, "فروشگاه‌ها", "ورود به فروشگاه", "background:rgba(16,185,129,.12); color:#10b981;"), + new(RouteConstants.Profile.Wallet, Icons.Material.Filled.AccountBalanceWallet, "کیف پول", "مدیریت و تاریخچه", "background:rgba(16,185,129,.12); color:#10b981;"), + new(RouteConstants.Profile.WithdrawalRequests, Icons.Material.Filled.RequestPage, "درخواست برداشت", "ثبت و پیگیری", "background:rgba(245,158,11,.12); color:#f59e0b;"), + new(RouteConstants.Club.Membership, Icons.Material.Filled.CardMembership, "باشگاه مشتریان", "عضویت و مزایا", "background:rgba(168,85,247,.12); color:#a855f7;"), + new(RouteConstants.Network.Statistics, Icons.Material.Filled.Groups, "آمار باشگاه", "جزئیات باشگاه", "background:rgba(59,130,246,.12); color:#3b82f6;"), + new(RouteConstants.Commission.Dashboard, Icons.Material.Filled.Payments, "پاداش", "درآمد و تاریخچه", "background:rgba(16,185,129,.12); color:#10b981;"), + }; + + private record DashTile(string Href, string Icon, string Title, string Subtitle, string AvatarStyle); + // Address management private List _addresses = new(); private bool _isLoadingAddresses; @@ -64,6 +81,7 @@ public partial class Index await LoadUserProfile(); await LoadAddresses(); await LoadWallet(); + await LoadLatestPost(); // نمایش Modal قرارداد باشگاه اگر پکیج خریده ولی باشگاه فعال نشده await CheckAndShowClubContractModal(); @@ -134,19 +152,47 @@ public partial class Index private static string FormatPrice(long price) => string.Format("{0:N0} تومان", price); + private async Task LoadLatestPost() + { + try + { + var posts = await BlogPostService.GetFeaturedPostsAsync(1); + if (!posts.Any()) + { + var result = await BlogPostService.GetPublishedPostsAsync(page: 1, pageSize: 1); + posts = result.Posts; + } + _latestPost = posts.FirstOrDefault(); + } + catch + { + _latestPost = null; + } + StateHasChanged(); + } + private async Task LoadUserProfile() { try { - _userProfile = await UserContract.GetUserAsync(request: new()); - _updateUserRequest = _userProfile.Adapt(); + _userProfile = await UserContract.GetUserForCustomerAsync(new()); + _updateUserRequest = new UpdateCustomerProfileRequest + { + FirstName = _userProfile.FirstName, + LastName = _userProfile.LastName, + Email = _userProfile.Email, + NationalCode = _userProfile.NationalCode, + }; if (_userProfile.BirthDate != null) + { _date = _userProfile.BirthDate.ToDateTime(); + _updateUserRequest.BirthDate = _userProfile.BirthDate; + } } catch (Exception ex) { // Handle the case when user is not authenticated or API fails - _userProfile = new GetUserResponse(); + _userProfile = new GetUserForCustomerResponse(); } StateHasChanged(); } @@ -168,7 +214,7 @@ public partial class Index if (_date != null) _updateUserRequest.BirthDate = _date.Value.DateTimeToTimestamp(); - await UserContract.UpdateUserAsync(request: _updateUserRequest); + await UserContract.UpdateCustomerProfileAsync(request: _updateUserRequest); await LoadUserProfile(); Snackbar.Add("اطلاعات شخصی با موفقیت ذخیره شد.", Severity.Success); } diff --git a/src/FrontOffice.Main/Pages/Profile/PaymentCallback.razor b/src/FrontOffice.Main/Pages/Profile/PaymentCallback.razor index a02dcd3..89e42ae 100644 --- a/src/FrontOffice.Main/Pages/Profile/PaymentCallback.razor +++ b/src/FrontOffice.Main/Pages/Profile/PaymentCallback.razor @@ -3,6 +3,7 @@ @using Blazored.LocalStorage @using CMSMicroservice.Protobuf.Protos.Package @using CMSMicroservice.Protobuf.Protos.User +@inject IJSRuntime JS نتیجه پرداخت | کارا بازار سلامت @@ -37,7 +38,7 @@ + OnClick="GoBack"> بازگشت به پروفایل } @@ -48,7 +49,7 @@ @_message + OnClick="GoBack"> بازگشت به پروفایل @@ -173,4 +174,6 @@ } private static string FormatPrice(long price) => string.Format("{0:N0} تومان", price); + + private async Task GoBack() => await JS.InvokeVoidAsync("history.back"); } diff --git a/src/FrontOffice.Main/Pages/Profile/Personal.razor b/src/FrontOffice.Main/Pages/Profile/Personal.razor index 0e72bbc..e5687cd 100644 --- a/src/FrontOffice.Main/Pages/Profile/Personal.razor +++ b/src/FrontOffice.Main/Pages/Profile/Personal.razor @@ -4,13 +4,10 @@ - - اطلاعات شخصی - بازگشت - + - + (); + _userProfile = await UserContract.GetUserForCustomerAsync(new()); + _updateUserRequest = new UpdateCustomerProfileRequest + { + FirstName = _userProfile.FirstName, + LastName = _userProfile.LastName, + Email = _userProfile.Email, + NationalCode = _userProfile.NationalCode, + }; if (_userProfile.BirthDate != null) _date = _userProfile.BirthDate.ToDateTime(); } @@ -56,7 +59,7 @@ public partial class Personal : ComponentBase if (_date != null) _updateUserRequest.BirthDate = _date.Value.DateTimeToTimestamp(); - await UserContract.UpdateUserAsync(_updateUserRequest); + await UserContract.UpdateCustomerProfileAsync(_updateUserRequest); Snackbar.Add("اطلاعات شخصی با موفقیت ذخیره شد.", Severity.Success); } catch (Exception ex) diff --git a/src/FrontOffice.Main/Pages/Profile/Settings.razor b/src/FrontOffice.Main/Pages/Profile/Settings.razor index 264c049..83d6dc3 100644 --- a/src/FrontOffice.Main/Pages/Profile/Settings.razor +++ b/src/FrontOffice.Main/Pages/Profile/Settings.razor @@ -4,10 +4,7 @@ - - تنظیمات حساب - بازگشت - + diff --git a/src/FrontOffice.Main/Pages/Profile/Settings.razor.cs b/src/FrontOffice.Main/Pages/Profile/Settings.razor.cs index df0f1d5..df1d6b6 100644 --- a/src/FrontOffice.Main/Pages/Profile/Settings.razor.cs +++ b/src/FrontOffice.Main/Pages/Profile/Settings.razor.cs @@ -1,5 +1,4 @@ using CMSMicroservice.Protobuf.Protos.User; -using Mapster; using Microsoft.AspNetCore.Components; using MudBlazor; @@ -16,8 +15,18 @@ public partial class Settings : ComponentBase { try { - var user = await UserContract.GetUserAsync(new()); - _request = user.Adapt(); + var user = await UserContract.GetUserForCustomerAsync(new()); + _request = new UpdateUserRequest + { + Id = user.Id, + FirstName = user.FirstName, + LastName = user.LastName, + Email = user.Email, + NationalCode = user.NationalCode, + EmailNotifications = user.EmailNotifications, + SmsNotifications = user.SmsNotifications, + PushNotifications = user.PushNotifications, + }; } catch { diff --git a/src/FrontOffice.Main/Pages/Profile/Tree.razor b/src/FrontOffice.Main/Pages/Profile/Tree.razor index bb50fbf..1720fac 100644 --- a/src/FrontOffice.Main/Pages/Profile/Tree.razor +++ b/src/FrontOffice.Main/Pages/Profile/Tree.razor @@ -5,10 +5,7 @@ - - شجره‌نامه - بازگشت - + diff --git a/src/FrontOffice.Main/Pages/Profile/Wallet.razor b/src/FrontOffice.Main/Pages/Profile/Wallet.razor index a42716f..98a94c1 100644 --- a/src/FrontOffice.Main/Pages/Profile/Wallet.razor +++ b/src/FrontOffice.Main/Pages/Profile/Wallet.razor @@ -4,10 +4,7 @@ - - کیف پول - بازگشت - + diff --git a/src/FrontOffice.Main/Pages/Profile/WithdrawalRequests.razor b/src/FrontOffice.Main/Pages/Profile/WithdrawalRequests.razor index 6703628..f0fa1f5 100644 --- a/src/FrontOffice.Main/Pages/Profile/WithdrawalRequests.razor +++ b/src/FrontOffice.Main/Pages/Profile/WithdrawalRequests.razor @@ -4,10 +4,7 @@ - - درخواست‌های برداشت - بازگشت به کیف پول - + @@ -100,7 +97,7 @@ @if (_isLoading) { - + } else if (!_withdrawals.Any()) { diff --git a/src/FrontOffice.Main/Pages/RegisterWizard.razor b/src/FrontOffice.Main/Pages/RegisterWizard.razor index 93e2590..97bb624 100644 --- a/src/FrontOffice.Main/Pages/RegisterWizard.razor +++ b/src/FrontOffice.Main/Pages/RegisterWizard.razor @@ -6,10 +6,11 @@ - - ثبت‌نام سه - مرحله‌ای - + + + ثبت‌نام سه مرحله‌ای + + فقط در چند دقیقه حساب خود را فعال کنید @@ -18,34 +19,47 @@ - - - - + + + + + + مزایای ثبت‌نام آنلاین - - • تایید سریع و آنلاین شماره موبایل
- • تکمیل اطلاعات هویتی بدون مراجعه حضوری
- • دریافت نسخه‌ی دیجیتال قرارداد برای بررسی دقیق -
+ + + + تایید سریع و آنلاین شماره موبایل + + + + تکمیل اطلاعات هویتی بدون مراجعه حضوری + + + + دریافت نسخه‌ی دیجیتال قرارداد برای بررسی دقیق + +
- + @if (_completed) { - - + + + + درخواست شما ثبت شد - + تیم ما پس از بررسی اطلاعات با شما تماس خواهد گرفت. می‌توانید از طریق داشبورد وضعیت ثبت‌نام را دنبال کنید. - بازگشت به صفحه اصلی + - مراحل ثبت‌نام - ۳ مرحله + مراحل ثبت‌نام + + مرحله @(_activeStep + 1) از ۳ @if (_isSubmitting) { - + } - + - + @* Inline AuthDialog with captcha enabled *@ - + - - - + + + + + - + - + لطفاً قوانین و شرایط همکاری را با دقت مطالعه کنید و در صورت موافقت، تیک - تایید را فعال نمایید. همچنین می‌توانید نسخه‌ی قرارداد را دانلود و ذخیره - کنید. + تایید را فعال نمایید. بخشی از قوانین: @@ -106,47 +122,44 @@ - - - دانلود/پرینت قرارداد نمونه - - فرمت: PDF - + + دانلود قرارداد نمونه (PDF) + - - + @if (_isAuthenticated && _activeStep == 1) { - خروج از حساب } else { - - مرحله قبل + مرحله قبل } - + @if (_isSubmitting) + { + + } @_nextButtonText - - } diff --git a/src/FrontOffice.Main/Pages/RegisterWizard.razor.cs b/src/FrontOffice.Main/Pages/RegisterWizard.razor.cs index 54c1175..02891d0 100644 --- a/src/FrontOffice.Main/Pages/RegisterWizard.razor.cs +++ b/src/FrontOffice.Main/Pages/RegisterWizard.razor.cs @@ -23,13 +23,14 @@ public partial class RegisterWizard private bool _completed; private AuthDialog? _authDialog; private MudStepper? _mudStepper; - private UpdateUserRequest _updateUserRequest = new(); + private UpdateCustomerProfileRequest _updateUserRequest = new(); private bool _isAuthenticated; private CancellationTokenSource? _operationCts; private UserAuthInfo _userAuthInfo; private Guid signGuid = Guid.NewGuid(); private const string TokenStorageKey = "auth:token"; private readonly DialogOptions _normalWidth = new() { MaxWidth = MaxWidth.ExtraSmall, FullWidth = true }; + private bool _initialDataLoaded; [Inject] private AuthService AuthService { get; set; } = default!; [Inject] private IDeviceDetector _deviceDetector { get; set; } = default!; @@ -55,6 +56,7 @@ public partial class RegisterWizard if (_userAuthInfo.IsSignMainContract) { Navigation.NavigateTo(RouteConstants.Profile.Index); + return; } if (_activeStep == 0) @@ -63,8 +65,9 @@ public partial class RegisterWizard _activeStep = 1; await InvokeAsync(StateHasChanged); } - else if (_activeStep == 1) + else if (_activeStep == 1 && !_initialDataLoaded) { + _initialDataLoaded = true; try { var existUser = await UserContract.GetUserForCustomerAsync(new GetUserForCustomerRequest()); @@ -95,7 +98,6 @@ public partial class RegisterWizard } } - await base.OnAfterRenderAsync(firstRender); } @@ -355,7 +357,7 @@ public partial class RegisterWizard _updateUserRequest.NationalCode = _model.NationalCode.PersianToEnglish(); - await UserContract.UpdateUserAsync(request: _updateUserRequest); + await UserContract.UpdateCustomerProfileAsync(request: _updateUserRequest); Snackbar.Add("اطلاعات شخصی با موفقیت ذخیره شد.", Severity.Success); return true; } diff --git a/src/FrontOffice.Main/Pages/Store/Cart.razor b/src/FrontOffice.Main/Pages/Store/Cart.razor index 9b72d10..60f8a38 100644 --- a/src/FrontOffice.Main/Pages/Store/Cart.razor +++ b/src/FrontOffice.Main/Pages/Store/Cart.razor @@ -5,7 +5,7 @@ - سبد خرید + @if (CartData.Items.Count == 0) { @@ -30,8 +30,8 @@ - + @context.Title @if (context.Discount > 0 || !string.IsNullOrWhiteSpace(context.Created) || !string.IsNullOrWhiteSpace(context.Description)) @@ -86,8 +86,8 @@ - + @item.Title @if (item.Discount > 0) @@ -154,8 +154,8 @@ } - مبلغ قابل پرداخت: - @FormatPrice(TotalWithVAT) تومان + مبلغ قابل پرداخت: + @FormatPrice(TotalWithVAT) تومان @@ -185,8 +185,8 @@ } - مبلغ قابل پرداخت: - @FormatPrice(TotalWithVAT) تومان + مبلغ قابل پرداخت: + @FormatPrice(TotalWithVAT) تومان diff --git a/src/FrontOffice.Main/Pages/Store/Cart.razor.cs b/src/FrontOffice.Main/Pages/Store/Cart.razor.cs index 9977d37..ee5adbd 100644 --- a/src/FrontOffice.Main/Pages/Store/Cart.razor.cs +++ b/src/FrontOffice.Main/Pages/Store/Cart.razor.cs @@ -66,7 +66,7 @@ public partial class Cart : ComponentBase, IDisposable private long VATAmount => VAT.CalculateVAT(CartData.Total); private static string GetProductImageUrl(string? imageUrl) - => string.IsNullOrWhiteSpace(imageUrl) ? "/images/product-placeholder.svg" : imageUrl; + => string.IsNullOrWhiteSpace(imageUrl) ? "/images/product-placeholder.svg" : imageUrl.TrimStart('/'); public void Dispose() { diff --git a/src/FrontOffice.Main/Pages/Store/Categories.razor b/src/FrontOffice.Main/Pages/Store/Categories.razor index c81de5d..81cc383 100644 --- a/src/FrontOffice.Main/Pages/Store/Categories.razor +++ b/src/FrontOffice.Main/Pages/Store/Categories.razor @@ -5,9 +5,9 @@ دسته‌بندی‌ها + - دسته‌بندی محصولات از بین درخت دسته‌بندی‌ها انتخاب کنید تا محصولات آن دسته را مشاهده نمایید. diff --git a/src/FrontOffice.Main/Pages/Store/CheckoutSummary.razor b/src/FrontOffice.Main/Pages/Store/CheckoutSummary.razor index 23c3fd7..95d7974 100644 --- a/src/FrontOffice.Main/Pages/Store/CheckoutSummary.razor +++ b/src/FrontOffice.Main/Pages/Store/CheckoutSummary.razor @@ -5,6 +5,7 @@ خلاصه خرید + @@ -30,7 +31,7 @@ @foreach (var address in _addresses) { @@ -84,8 +85,8 @@ - + @item.Title @FormatPrice(item.LineTotal) @@ -118,8 +119,8 @@ } - مبلغ قابل پرداخت: - @FormatPrice(VAT.AddVAT(Cart.Total)) + مبلغ قابل پرداخت: + @FormatPrice(VAT.AddVAT(Cart.Total)) diff --git a/src/FrontOffice.Main/Pages/Store/CheckoutSummary.razor.cs b/src/FrontOffice.Main/Pages/Store/CheckoutSummary.razor.cs index ad5c5c3..7ca7aad 100644 --- a/src/FrontOffice.Main/Pages/Store/CheckoutSummary.razor.cs +++ b/src/FrontOffice.Main/Pages/Store/CheckoutSummary.razor.cs @@ -113,5 +113,5 @@ public partial class CheckoutSummary : ComponentBase private long CalculateVAT() => VAT.CalculateVAT(Cart.Total); private static string GetProductImageUrl(string? imageUrl) - => string.IsNullOrWhiteSpace(imageUrl) ? "/images/product-placeholder.svg" : imageUrl; + => string.IsNullOrWhiteSpace(imageUrl) ? "/images/product-placeholder.svg" : imageUrl.TrimStart('/'); } \ No newline at end of file diff --git a/src/FrontOffice.Main/Pages/Store/OrderDetail.razor b/src/FrontOffice.Main/Pages/Store/OrderDetail.razor index 98e25cf..823f6a5 100644 --- a/src/FrontOffice.Main/Pages/Store/OrderDetail.razor +++ b/src/FrontOffice.Main/Pages/Store/OrderDetail.razor @@ -6,24 +6,24 @@ @if (_loading) { - - - + } else if (_order is null) { - سفارش یافت نشد. - بازگشت + } else { + - سفارش #@_order.Id @if (_order.PaymentDate != null) { تاریخ پرداخت: @_order.PaymentDate.ToDateTime().MiladiToJalaliWithTime() @@ -46,8 +46,8 @@ else - + @context.ProductTitle @@ -65,8 +65,8 @@ else - + @it.ProductTitle @@ -100,15 +100,15 @@ else - مبلغ قابل پرداخت: - @FormatPrice(_order.VatInfo.TotalAmount) + مبلغ قابل پرداخت: + @FormatPrice(_order.VatInfo.TotalAmount) } else { - مبلغ قابل پرداخت: - @FormatPrice(subtotal) + مبلغ قابل پرداخت: + @FormatPrice(subtotal) } diff --git a/src/FrontOffice.Main/Pages/Store/OrderDetail.razor.cs b/src/FrontOffice.Main/Pages/Store/OrderDetail.razor.cs index b492929..5961f8e 100644 --- a/src/FrontOffice.Main/Pages/Store/OrderDetail.razor.cs +++ b/src/FrontOffice.Main/Pages/Store/OrderDetail.razor.cs @@ -45,6 +45,6 @@ public partial class OrderDetail : ComponentBase } private static string GetProductImageUrl(string? imageUrl) - => string.IsNullOrWhiteSpace(imageUrl) ? "/images/product-placeholder.svg" : UrlUtility.DownloadUrl+imageUrl; + => string.IsNullOrWhiteSpace(imageUrl) ? "/images/product-placeholder.svg" : imageUrl.TrimStart('/'); } diff --git a/src/FrontOffice.Main/Pages/Store/OrderTracking.razor b/src/FrontOffice.Main/Pages/Store/OrderTracking.razor index 1207505..ccc6af8 100644 --- a/src/FrontOffice.Main/Pages/Store/OrderTracking.razor +++ b/src/FrontOffice.Main/Pages/Store/OrderTracking.razor @@ -4,6 +4,7 @@ پیگیری سفارش + @if (_loading) { @@ -15,7 +16,7 @@ { سفارش یافت نشد. + OnClick="GoBack"> بازگشت به سفارش‌ها } diff --git a/src/FrontOffice.Main/Pages/Store/OrderTracking.razor.cs b/src/FrontOffice.Main/Pages/Store/OrderTracking.razor.cs index 84561ed..c15808c 100644 --- a/src/FrontOffice.Main/Pages/Store/OrderTracking.razor.cs +++ b/src/FrontOffice.Main/Pages/Store/OrderTracking.razor.cs @@ -1,6 +1,7 @@ using CMSMicroservice.Protobuf.Protos.UserOrder; using FrontOffice.Main.Utilities; using Microsoft.AspNetCore.Components; +using Microsoft.JSInterop; using Messages = CMSMicroservice.Protobuf.Protos; namespace FrontOffice.Main.Pages.Store; @@ -8,6 +9,7 @@ namespace FrontOffice.Main.Pages.Store; public partial class OrderTracking : ComponentBase { [Inject] private OrderService OrderService { get; set; } = default!; + [Inject] private IJSRuntime JS { get; set; } = default!; [Parameter] public long id { get; set; } @@ -123,4 +125,6 @@ public partial class OrderTracking : ComponentBase public bool IsCurrent { get; set; } public string Date { get; set; } = ""; } + + private async Task GoBack() => await JS.InvokeVoidAsync("history.back"); } diff --git a/src/FrontOffice.Main/Pages/Store/Orders.razor b/src/FrontOffice.Main/Pages/Store/Orders.razor index a40cd5a..811451b 100644 --- a/src/FrontOffice.Main/Pages/Store/Orders.razor +++ b/src/FrontOffice.Main/Pages/Store/Orders.razor @@ -4,12 +4,10 @@ سفارشات من - سفارشات من + @if (_loading) { - - - + } else if (_orders.Count == 0) { diff --git a/src/FrontOffice.Main/Pages/Store/ProductDetail.razor b/src/FrontOffice.Main/Pages/Store/ProductDetail.razor index 79b17aa..3426a9a 100644 --- a/src/FrontOffice.Main/Pages/Store/ProductDetail.razor +++ b/src/FrontOffice.Main/Pages/Store/ProductDetail.razor @@ -6,23 +6,22 @@ @if (_loading) { - - - + } else if (_product is null) { - محصول یافت نشد. - بازگشت - + } else { + @if (_product is not null) @@ -32,7 +31,9 @@ else @if (!string.IsNullOrWhiteSpace(MainImageUrl)) { -
@MainImageAlt
+ } else { @@ -45,7 +46,7 @@ else @foreach (var item in _galleryItems) { - + }
diff --git a/src/FrontOffice.Main/Pages/Store/Products.razor b/src/FrontOffice.Main/Pages/Store/Products.razor index 88d5cca..b3cdb04 100644 --- a/src/FrontOffice.Main/Pages/Store/Products.razor +++ b/src/FrontOffice.Main/Pages/Store/Products.razor @@ -3,7 +3,18 @@ محصولات - + + + + + + فروشگاه کارا بازار سلامت + + محصولات با کیفیت با ضمانت اصالت و ارسال سریع + + + + @* ردیف اول: جستجو و دکمه‌ها *@ @@ -44,25 +55,25 @@ Margin="Margin.Dense" AnchorOrigin="Origin.BottomCenter" AdornmentIcon="@Icons.Material.Filled.Sort"> - +
گران‌ترین
- +
ارزان‌ترین
- +
جدیدترین
- +
الفبایی diff --git a/src/FrontOffice.Main/Pages/_Host.cshtml b/src/FrontOffice.Main/Pages/_Host.cshtml index e22f5de..c01aef2 100644 --- a/src/FrontOffice.Main/Pages/_Host.cshtml +++ b/src/FrontOffice.Main/Pages/_Host.cshtml @@ -8,15 +8,19 @@ + + + - - - + + + + @@ -42,6 +46,7 @@ +