Compare commits

..

39 Commits

Author SHA1 Message Date
masoodafar-web 3d84e726db Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Successful in 6m27s
2026-08-09 02:33:27 +03:30
masoodafar-web 9203dc7924 Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Successful in 6m24s
2026-08-07 14:38:22 +03:30
masoodafar-web 4989cd00e0 Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Failing after 16m3s
2026-07-26 00:52:30 +03:30
masoodafar-web bed35935ad Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Failing after 17m17s
2026-07-15 22:02:31 +03:30
masoodafar-web ceaaac3e23 Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Failing after 18m42s
2026-07-03 06:04:56 +03:30
masoodafar-web db35814ce3 Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Has been cancelled
2026-07-03 05:43:14 +03:30
masoodafar-web 7a5a2110c6 feat: add SEO configuration for production environment
Build and Deploy to Production / build-and-deploy (push) Failing after 19m58s
- Introduced SEO settings in appsettings.Production.json, including SiteUrl, SiteName, and DefaultDescription.
- These additions enhance the application's search engine optimization capabilities, improving visibility and user engagement.
2026-07-01 21:48:49 +03:30
masoodafar-web 8dbc64cd86 Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Has been cancelled
2026-07-01 21:45:43 +03:30
masoodafar-web deadd197e6 Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Has been cancelled
2026-07-01 21:31:49 +03:30
masoodafar-web 671c8407e5 Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Has been cancelled
2026-06-30 02:50:03 +03:30
masoodafar-web 1956dcc5f4 Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Has been cancelled
2026-06-30 02:28:13 +03:30
masoodafar-web d59d829d5f Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Failing after 17m21s
2026-06-26 19:30:51 +03:30
masoodafar-web 3c216feb32 Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Failing after 15m3s
2026-06-19 02:38:15 +03:30
masoodafar-web 032b8eb448 Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Successful in 8m14s
2026-06-08 01:55:28 +03:30
masoodafar-web 088ea8dc7f Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Successful in 6m36s
2026-05-29 19:59:19 +03:30
masoodafar-web cd92a2f7b0 Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Successful in 4m24s
2026-05-14 20:12:20 +03:30
masoodafar-web 0c9eb5f9ef Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Successful in 4m25s
2026-05-14 02:07:56 +03:30
masoodafar-web 94fbf49fb9 Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Successful in 4m51s
2026-05-14 01:56:18 +03:30
masoodafar-web 4330a5eaa5 Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Successful in 4m16s
2026-05-14 01:12:35 +03:30
masoodafar-web 1e5e6bcf3d Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Successful in 5m50s
2026-05-04 00:04:13 +03:30
masoodafar-web d751c06a67 Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Successful in 4m5s
2026-05-01 00:16:24 +03:30
masoodafar-web 1c5aa0ce15 Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Successful in 14m28s
- Proto version: 0.0.192
- Remove local nupkg source from NuGet.config (breaks Docker builds)
2026-03-15 03:26:17 +03:30
masoodafar-web caaf28b08e fix: update CMS protobuf package version to 0.0.191 and comment out local package reference
Build and Deploy to Production / build-and-deploy (push) Successful in 6m22s
2026-02-28 05:07:54 +03:30
masoodafar-web 1914f703db Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Failing after 2m40s
2026-02-28 04:47:34 +03:30
masoodafar-web a606cd93cd fix: rename wallet labels — اصلی، اعتباری، پاداش تیمی
Build and Deploy to Production / build-and-deploy (push) Successful in 6m19s
- عادی → اصلی (کیف پول اصلی)
- شبکه → پاداش تیمی
- پورسانت → پاداش تیمی
- موجودی کیف پول → موجودی کیف پول اصلی

Files: Wallet.razor, WalletService.cs, MagicWallet.razor,
       Profile/Index.razor, PaymentCallback.razor
2026-02-23 23:26:52 +03:30
masoodafar-web 8b78235ef5 chore: prevent appsettings.Production.json from merging — keep ours strategy
Build and Deploy to Production / build-and-deploy (push) Successful in 6m27s
2026-02-23 00:03:00 +03:30
masoodafar-web f02d082d7b fix: correct GwUrl in Production config to cms.kbs2.ir
Build and Deploy to Production / build-and-deploy (push) Successful in 6m23s
2026-02-22 22:29:17 +03:30
masoodafar-web a11a3765b8 Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Has been cancelled
2026-02-22 22:22:43 +03:30
admin 3cf5ad429d Update src/FrontOffice.Main/Shared/Footer.razor
Build and Deploy to Production / build-and-deploy (push) Successful in 6m39s
2026-02-22 10:25:35 +00:00
masoodafar-web 710ddbe261 Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Successful in 6m2s
2026-02-21 20:38:23 +03:30
masoodafar-web e2ca65ed98 Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Successful in 6m42s
2026-02-18 01:19:34 +03:30
masoodafar-web ae9b448a64 merge: kub-stage → production (1:1 square images + site pages + landing animation fix)
Build and Deploy to Production / build-and-deploy (push) Has been cancelled
2026-02-18 01:01:44 +03:30
Masoud 1ea928118d fix: update references from تخفیفی to اعتباری across multiple files
Build and Deploy to Production / build-and-deploy (push) Successful in 5m46s
2026-02-17 15:26:09 +03:30
Masoud 8f2d739d1f fix: update references from تخفیفی to اعتباری across multiple files
Build and Deploy to Production / build-and-deploy (push) Has been cancelled
2026-02-17 15:24:13 +03:30
masoodafar-web 8e89d5e064 Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Successful in 5m43s
2026-02-17 01:59:40 +03:30
masoodafar-web 8bd961ade1 Fix production workflow: use SERVER_PASSWORD, add docker login, use set image for deploy
Build and Deploy to Production / build-and-deploy (push) Successful in 5m39s
2026-02-17 01:24:06 +03:30
masoodafar-web 750d4c6b97 Remove staging workflow files from production branch to prevent cross-deployment 2026-02-17 01:15:59 +03:30
masoodafar-web 276a4c20f2 Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Successful in 1m12s
Features merged:
- Discount store UI improvements (100% auto-discount, VAT breakdown)
- PaymentStatus display on orders (Pending/Success/Failed/Refunded)
- Failed payment UX with re-order link
- Product discount badges show actual percent
- Various UI and bug fixes

Production workflow preserved.
2026-02-17 00:55:52 +03:30
masoodafar-web 98a68074bc chore: Update production workflow to use Nexus registry (no proxy needed)
Build and Deploy to Production / build-and-deploy (push) Failing after 0s
- Changed Dockerfile base images to use 194.5.195.53:32082 (Nexus)
- Updated workflow to use docker-sshpass image from Nexus
- Removed HTTP_PROXY/HTTPS_PROXY dependencies
- Changed deploy method to SSH instead of kubeconfig
- Registry: 194.5.195.53:30080 (Gitea internal)
- K8S Server: 45.149.79.127 (Production)
2026-01-29 22:04:31 +03:30
27 changed files with 241 additions and 274 deletions
-46
View File
@@ -1,46 +0,0 @@
name: Push nuget and docker image Actions Workflow
on:
push:
branches:
- stage-new
jobs:
Deploy:
runs-on: windows
steps:
- name: Checkout
uses: https://git.afrino.co/actions/checkout@v3
- name: Setup dotnet
uses: https://git.afrino.co/actions/setup-dotnet@v3
with:
dotnet-version: 7.0.x
- name: Remove Package Source
run: dotnet nuget remove source FourSat
continue-on-error: true
- name: Add Package Source
run: dotnet nuget add source --name FourSat --username systemuser --password sZSA7PTiv3pUSQZ https://git.afrino.co/api/packages/FourSat/nuget/index.json --store-password-in-clear-text
- name: Install dependencies
run: dotnet restore ".\src\FrontOffice.Main\FrontOffice.Main.csproj"
- name: Build
run: dotnet build ".\src\FrontOffice.Main\FrontOffice.Main.csproj" --configuration Release --no-restore
- name: Test
run: dotnet test ".\src\FrontOffice.Main\FrontOffice.Main.csproj" --no-restore --verbosity normal
- name: Recycle Apppool
run: |
& "C:\Windows\System32\inetsrv\appcmd.exe" recycle apppool /apppool.name:kbs1.ir
shell: powershell
- name: Stop Website
run: |
& "C:\Windows\System32\inetsrv\appcmd.exe" stop site /site.name:kbs1.ir
shell: powershell
- name: Publish
run: dotnet publish ".\src\FrontOffice.Main\FrontOffice.Main.csproj" -c Release -o publish
- name: Copy Publish To IIS Directory
run: Get-ChildItem -Path "publish\*" | Copy-Item -Destination "E:\kbs1.ir\kbs1.ir\" -Recurse -Force
- name: Start Website
run: |
& "C:\Windows\System32\inetsrv\appcmd.exe" start site /site.name:kbs1.ir
shell: powershell
+5 -4
View File
@@ -8,7 +8,7 @@ on:
env:
REGISTRY: 194.5.195.53:30080
IMAGE_NAME: admin/frontoffice
K8S_SERVER: 194.5.195.53
K8S_SERVER: 45.149.79.127
jobs:
build-and-deploy:
@@ -40,7 +40,7 @@ jobs:
done
if ! docker info >/dev/null 2>&1; then
echo "❌ Docker daemon failed to start after 3 minutes"
echo "❌ Docker daemon failed to start"
exit 1
fi
@@ -56,9 +56,10 @@ jobs:
- name: Build Docker Image
run: |
cd src
DOCKER_BUILDKIT=0 docker build --network host -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} \
DOCKER_BUILDKIT=0 docker build --network host -f FrontOffice.Main/Dockerfile \
-t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} \
-t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:prod \
-f FrontOffice.Main/Dockerfile .
.
- name: Push to Registry
run: |
+1 -1
View File
@@ -14,7 +14,7 @@
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
# Mono auto generated files
mono_crash.*
# Build results
+1
View File
@@ -101,6 +101,7 @@
| 6.6 | `UserOrderContract.SubmitShopBuyOrder` | `UserOrderContract` | `/checkout-summary` | ثبت سفارش و پرداخت از کیف پول |
| 6.7 | `OrderService.GetOrdersAsync` | `UserOrderContract.GetCustomerOrders` | `/orders` | لیست سفارش‌های کاربر |
| 6.8 | `OrderService.GetOrderAsync` | `UserOrderContract.GetCustomerOrder` | `/order/{id}`, `/order-tracking/{id}` | جزئیات یک سفارش |
| 6.9 | `OrderService.CalculateOrderPVAsync` | `UserOrderContract.CalculateOrderPV` | `/order/{id}` | محاسبه PV سفارش |
| 6.10 | `VATService.GetRateAsync` | `UserOrderContract` (via scope) | Store/Discount صفحات قیمت | نرخ مالیات بر ارزش افزوده (کش روزانه) |
| 6.11 | `WalletService.GetBalancesAsync` | `UserWalletContract` | `/checkout-summary` | بررسی موجودی برای پرداخت سفارش |
+1 -1
View File
@@ -11,7 +11,7 @@
<ItemGroup>
<PackageReference Include="DateTimeConverterCL" Version="1.0.0" />
<!-- Replace all FrontOffice.BFF protobuf packages with CMS protobuf -->
<PackageReference Include="Foursat.CMSMicroservice.Protobuf" Version="0.0.210" />
<PackageReference Include="Foursat.CMSMicroservice.Protobuf" Version="0.0.204" />
<!-- <ProjectReference Include="../../../CMS/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj" />-->
<PackageReference Include="MudBlazor" Version="8.14.0" />
<PackageReference Include="Blazored.LocalStorage" Version="4.5.0" />
+75 -1
View File
@@ -71,6 +71,80 @@
</MudStack>
}
</MudPaper>
<!-- Address Selection -->
<MudPaper Elevation="2" Class="pa-4 rounded-lg">
<MudText Typo="Typo.h5" Class="mb-4">انتخاب آدرس</MudText>
@if (_isLoadingAddresses)
{
<MudStack AlignItems="AlignItems.Center" Class="py-4">
<MudProgressCircular Color="Color.Primary" Indeterminate="true" Size="Size.Medium" />
<MudText Typo="Typo.body2" Class="mud-text-secondary mt-2">بارگذاری آدرس‌ها...</MudText>
</MudStack>
}
else if (_addresses.Any())
{
<MudStack Spacing="2">
@foreach (var address in _addresses)
{
<MudPaper Outlined="@(_selectedAddress?.Id != address.Id)"
Elevation="@(_selectedAddress?.Id == address.Id ? 4 : 0)"
Class="pa-3 rounded-xl cursor-pointer"
Style="@(_selectedAddress?.Id == address.Id ? "border: 2px solid var(--mud-palette-primary);" : "")"
@onclick="() => SetAddressAsDefault(address.Id)">
<MudStack Spacing="1">
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
<MudCheckBox @bind-Value="address.IsDefault"
Disabled="true"
Color="Color.Primary" />
<MudText Typo="Typo.subtitle2">@(address.Title)</MudText>
@if (address.IsDefault)
{
<MudChip T="string" Color="Color.Success" Variant="Variant.Filled" Size="Size.Small">پیش‌فرض</MudChip>
}
@if (!address.IsDefault)
{
@if (_isSettingDefaultAddress && _settingDefaultAddressId == address.Id)
{
<MudProgressCircular Size="Size.Small" Color="Color.Primary" Indeterminate="true" />
}
else
{
<MudButton Variant="Variant.Text"
Color="Color.Primary"
Size="Size.Small"
OnClick="() => SetAddressAsDefault(address.Id)">
تنظیم به عنوان پیش‌فرض
</MudButton>
}
}
</MudStack>
<MudStack Row="true">
<MudText Typo="Typo.body2" Class="mud-text-secondary">@(address.Address)</MudText>
<MudSpacer />
<MudText Typo="Typo.caption" Class="mud-text-secondary">کد پستی: @(address.PostalCode)</MudText>
</MudStack>
</MudStack>
</MudPaper>
}
</MudStack>
}
else
{
<MudStack AlignItems="AlignItems.Center" Class="py-4">
<MudIcon Icon="@Icons.Material.Filled.LocationOff" Size="Size.Large" Color="Color.Default" />
<MudText Typo="Typo.body2" Class="mud-text-secondary mt-2">آدرسی ثبت نشده است.</MudText>
<MudButton Variant="Variant.Outlined"
Color="Color.Primary"
StartIcon="@Icons.Material.Filled.Add"
OnClick="() => Navigation.NavigateTo(RouteConstants.Profile.Index)"
Class="mt-2">
افزودن آدرس
</MudButton>
</MudStack>
}
</MudPaper>
</MudStack>
</MudItem>
@@ -167,7 +241,7 @@
@if (!CanProceedToPayment)
{
<MudText Typo="Typo.caption" Color="Color.Error" Align="Align.Center">
لطفاً پکیج را انتخاب کنید.
لطفاً پکیج و آدرس را انتخاب کنید.
</MudText>
}
</MudStack>
+72 -7
View File
@@ -1,4 +1,6 @@
using CMSMicroservice.Protobuf.Protos.Package;
using CMSMicroservice.Protobuf.Protos.UserAddress;
using FrontOffice.Main.Utilities;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
@@ -11,10 +13,14 @@ public partial class Checkout
{
[Inject] private PackageService PackageService { get; set; } = default!;
[Inject] private PackageContract.PackageContractClient PackageClient { get; set; } = default!;
[Inject] private UserAddressContract.UserAddressContractClient UserAddressContract { get; set; } = default!;
[Parameter] public long? PackageId { get; set; }
private Pack? _selectedPackage;
private List<CustomerAddressModel> _addresses = new();
private CustomerAddressModel? _selectedAddress;
private bool _isLoadingAddresses;
private bool _isProcessingPayment;
// Discount code
@@ -25,11 +31,16 @@ public partial class Checkout
private long _discountAmount;
private long _finalPrice;
private bool CanProceedToPayment => _selectedPackage != null;
// Address management
private bool _isSettingDefaultAddress;
private long? _settingDefaultAddressId;
private bool CanProceedToPayment => _selectedPackage != null && _selectedAddress != null;
protected override async Task OnInitializedAsync()
{
await LoadPackageDetails();
await LoadAddresses();
}
private async Task LoadPackageDetails()
@@ -61,6 +72,60 @@ public partial class Checkout
}
}
private async Task LoadAddresses()
{
_isLoadingAddresses = true;
try
{
var response = await UserAddressContract.GetCustomerAddressesAsync(request: new());
if (response?.Models?.Any() == true)
{
_addresses = response.Models.ToList();
// Select default address if available
_selectedAddress = _addresses.FirstOrDefault(a => a.IsDefault) ?? _addresses.First();
}
else
{
_addresses = new List<CustomerAddressModel>();
}
}
catch (Exception ex)
{
Snackbar.Add($"خطا در بارگذاری آدرس‌ها: {ex.Message}", Severity.Error);
_addresses = new List<CustomerAddressModel>();
}
finally
{
_isLoadingAddresses = false;
await InvokeAsync(StateHasChanged);
}
}
private async Task SetAddressAsDefault(long addressId)
{
if (_isSettingDefaultAddress) return;
_isSettingDefaultAddress = true;
_settingDefaultAddressId = addressId;
try
{
await UserAddressContract.SetAddressAsDefaultAsync(new() { Id = addressId });
await LoadAddresses(); // Reload addresses to reflect the change
Snackbar.Add("آدرس پیش‌فرض با موفقیت تغییر یافت.", Severity.Success);
}
catch (Exception ex)
{
Snackbar.Add($"خطا در تغییر آدرس پیش‌فرض: {ex.Message}", Severity.Error);
}
finally
{
_isSettingDefaultAddress = false;
_settingDefaultAddressId = null;
await InvokeAsync(StateHasChanged);
}
}
private async Task ApplyDiscountCode()
{
if (string.IsNullOrWhiteSpace(_discountCode))
@@ -92,7 +157,7 @@ public partial class Checkout
_finalPrice = _selectedPackage!.Price;
}
}
catch (Exception)
catch (Exception ex)
{
_discountMessage = "خطا در اعمال کد تخفیف.";
_discountApplied = false;
@@ -110,9 +175,9 @@ public partial class Checkout
{
if (_isProcessingPayment) return;
if (!CanProceedToPayment || _selectedPackage == null)
if (!CanProceedToPayment || _selectedPackage == null || _selectedAddress == null)
{
Snackbar.Add("لطفاً پکیج را انتخاب کنید.", Severity.Warning);
Snackbar.Add("لطفاً پکیج و آدرس را انتخاب کنید.", Severity.Warning);
return;
}
@@ -154,9 +219,9 @@ public partial class Checkout
private async Task DayaLoanPayment()
{
if (_selectedPackage == null)
if (_selectedAddress == null)
{
Snackbar.Add("لطفاً پکیج را انتخاب کنید.", Severity.Warning);
Snackbar.Add("لطفاً آدرس را انتخاب کنید.", Severity.Warning);
return;
}
@@ -165,4 +230,4 @@ public partial class Checkout
}
private record Pack(long Id, string Title, string Body, string Image, long Price, bool SupportsDirectPurchase = true, bool SupportsDayaPurchase = false);
}
}
@@ -22,11 +22,9 @@
else if (_addresses.Count == 0)
{
<MudAlert Severity="Severity.Warning">
هیچ آدرسی ثبت نشده است. می‌توانید همین‌جا آدرس جدید اضافه کنید.
هیچ آدرسی ثبت نشده است. لطفاً از بخش پروفایل آدرس خود را اضافه کنید.
</MudAlert>
<MudButton Class="mt-2" Variant="Variant.Outlined" Color="Color.Primary"
StartIcon="@Icons.Material.Filled.Add"
OnClick="OpenAddAddressDialog">افزودن آدرس</MudButton>
<MudButton Class="mt-2" Variant="Variant.Outlined" Href="/profile/addresses">افزودن آدرس</MudButton>
}
else
{
@@ -43,25 +41,13 @@
<MudText Typo="Typo.subtitle2">@address.Title</MudText>
<MudText Typo="Typo.body2" Class="mud-text-secondary">@address.Address</MudText>
</MudStack>
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
@if (address.IsDefault)
{
<MudChip T="string" Color="Color.Success" Variant="Variant.Outlined" Size="Size.Small">پیش‌فرض</MudChip>
}
<span @onclick:stopPropagation="true">
<MudIconButton Icon="@Icons.Material.Filled.Edit"
Size="Size.Small"
Color="Color.Primary"
aria-label="ویرایش آدرس"
OnClick="@(() => OpenEditAddressDialog(address))" />
</span>
</MudStack>
@if (address.IsDefault)
{
<MudChip T="string" Color="Color.Success" Variant="Variant.Outlined">پیش‌فرض</MudChip>
}
</MudStack>
</MudPaper>
}
<MudButton Class="mt-2" Variant="Variant.Text" Color="Color.Primary"
StartIcon="@Icons.Material.Filled.Add"
OnClick="OpenAddAddressDialog">افزودن آدرس جدید</MudButton>
</MudStack>
}
</MudPaper>
@@ -1,6 +1,5 @@
using Microsoft.AspNetCore.Components;
using CMSMicroservice.Protobuf.Protos.UserAddress;
using FrontOffice.Main.Pages.Profile.Components;
using FrontOffice.Main.Utilities;
using MudBlazor;
@@ -41,11 +40,7 @@ public partial class Checkout
}
await VAT.LoadAsync();
await DiscountCart.EnsureInitializedAsync();
var userInfo = await AuthService.GetUserAuthInfo();
if (userInfo.HasAddress)
await LoadAddresses();
else
_addresses = new();
await LoadAddresses();
}
private async Task LoadAddresses()
@@ -62,14 +57,12 @@ public partial class Checkout
else
{
_addresses = new();
_selectedAddress = null;
}
}
catch (Exception ex)
{
Snackbar.Add($"خطا در بارگذاری آدرس‌ها: {ex.Message}", Severity.Error);
_addresses = new();
_selectedAddress = null;
}
finally
{
@@ -78,30 +71,6 @@ public partial class Checkout
}
}
private async Task OpenAddAddressDialog()
{
var dialog = await DialogService.ShowAsync<AddAddressDialog>("افزودن آدرس جدید");
var result = await dialog.Result;
if (result is { Canceled: false })
{
await AuthService.RefreshTokenAsync();
await LoadAddresses();
}
}
private async Task OpenEditAddressDialog(CustomerAddressModel address)
{
var dialog = await DialogService.ShowAsync<EditAddressDialog>("ویرایش آدرس", new DialogParameters<EditAddressDialog>
{
{ x => x.Model, address }
});
var result = await dialog.Result;
if (result is { Canceled: false })
{
await LoadAddresses();
}
}
private async Task PlaceOrder()
{
if (!CanPlaceOrder || _selectedAddress is null)
@@ -1,4 +1,4 @@
using FrontOffice.Main.Utilities;
using CMSMicroservice.Protobuf.Protos.UserAddress;
using CMSMicroservice.Protobuf.Protos.UserAddress;
using FrontOffice.Main.Pages.Profile.Components;
using Microsoft.AspNetCore.Components;
@@ -9,7 +9,6 @@ namespace FrontOffice.Main.Pages.Profile;
public partial class Addresses : ComponentBase
{
[Inject] private UserAddressContract.UserAddressContractClient UserAddressContract { get; set; } = default!;
[Inject] private AuthService AuthService { get; set; } = default!;
private List<CustomerAddressModel> _addresses = new();
private bool _isLoadingAddresses;
@@ -43,10 +42,7 @@ public partial class Addresses : ComponentBase
var dialog = await DialogService.ShowAsync<AddAddressDialog>("افزودن آدرس جدید");
var result = await dialog.Result;
if (result is not null && !result.Canceled)
{
await LoadAddresses();
await AuthService.RefreshTokenAsync();
}
}
private async Task OpenEditAddressDialog(CustomerAddressModel address)
@@ -84,7 +80,6 @@ public partial class Addresses : ComponentBase
await UserAddressContract.DeleteCustomerAddressAsync(new() { Id = id });
Snackbar.Add("آدرس حذف شد.", Severity.Success);
await LoadAddresses();
await AuthService.RefreshTokenAsync();
}
catch (Exception ex)
{
@@ -110,11 +110,7 @@ public partial class OrganizationChart : IAsyncDisposable
activationWeekNumber = n.ActivationWeekNumber,
joinedAt = n.JoinedAt,
referralCode = n.ReferralCode,
packageName = n.PackageName ?? string.Empty,
goldLeftLegTotal = n.GoldLeftLegTotal,
goldRightLegTotal = n.GoldRightLegTotal,
silverLeftLegTotal = n.SilverLeftLegTotal,
silverRightLegTotal = n.SilverRightLegTotal
packageName = n.PackageName ?? string.Empty
}).ToArray();
await JSRuntime.InvokeVoidAsync("OrgChart.init", "org-chart-container", flatData, _dotNetHelper);
@@ -333,7 +333,6 @@ public partial class Index
if (!result.Canceled)
{
await LoadAddresses();
await AuthService.RefreshTokenAsync();
}
}
@@ -386,7 +385,6 @@ public partial class Index
});
Snackbar.Add("آدرس با موفقیت حذف شد.", Severity.Success);
await LoadAddresses();
await AuthService.RefreshTokenAsync();
}
catch (Exception ex)
{
@@ -493,6 +491,11 @@ public partial class Index
Snackbar.Add("لطفا اطلاعات شخصی خود را تکمیل کنید. (تاریخ تولد وارد نشده)", Severity.Error);
return;
}
if (!_addresses.Any())
{
Snackbar.Add("آدرس محل سکونت شما الزامی است!", Severity.Error);
return;
}
var url = "https://dayadiamond.ir/profile/creditpurchase/?merchantcode=56146364";
await JSRuntime.InvokeVoidAsync("open", url, "_blank");
@@ -31,7 +31,7 @@
<MudDivider Class="my-4" />
<MudText Typo="Typo.body2" Color="Color.Secondary">
موجودی کیف پول: @FormatPrice(_walletBalance)
موجودی کیف پول اصلی: @FormatPrice(_walletBalance)
</MudText>
<MudButton Color="Color.Primary" Variant="Variant.Filled" Class="mt-6"
@@ -58,19 +58,8 @@ public partial class WithdrawalRequests : ComponentBase
try
{
_isSubmittingWithdrawal = true;
string? normalizedIban = null;
if (_withdrawMethod == WithdrawalMethodClient.Cash)
{
normalizedIban = NormalizeIranianIban(_withdrawIban);
if (normalizedIban is null)
{
Snackbar.Add("فرمت شماره شبا معتبر نیست. باید IR و ۲۴ رقم باشد (خط تیره اختیاری است).", Severity.Warning);
return;
}
}
await WalletService.RequestWithdrawalAsync(_selectedPayout.Id, _withdrawMethod, normalizedIban);
_withdrawIban=_withdrawIban!.Trim().ToUpper().Replace("IR", "").Replace(" ", "");
await WalletService.RequestWithdrawalAsync(_selectedPayout.Id, _withdrawMethod, "IR"+_withdrawIban);
Snackbar.Add("درخواست برداشت ثبت شد.", Severity.Success);
// بروزرسانی لیست
@@ -136,26 +125,4 @@ public partial class WithdrawalRequests : ComponentBase
1 => "الماس",
_ => "-"
};
/// <summary>
/// فاصله و خط تیره را حذف می‌کند و شبا را به شکل IR + ۲۴ رقم برمی‌گرداند.
/// </summary>
private static string? NormalizeIranianIban(string? iban)
{
if (string.IsNullOrWhiteSpace(iban))
return null;
var normalized = iban.Trim().ToUpperInvariant()
.Replace(" ", "", StringComparison.Ordinal)
.Replace("-", "", StringComparison.Ordinal);
if (normalized.StartsWith("IR", StringComparison.Ordinal))
normalized = normalized[2..];
normalized = "IR" + normalized;
return System.Text.RegularExpressions.Regex.IsMatch(normalized, @"^IR\d{24}$")
? normalized
: null;
}
}
@@ -21,11 +21,9 @@
else if (_addresses.Count == 0)
{
<MudAlert Severity="Severity.Warning">
هیچ آدرسی ثبت نشده است. می‌توانید همین‌جا آدرس جدید اضافه کنید.
هیچ آدرسی ثبت نشده است. لطفاً از بخش پروفایل آدرس خود را اضافه کنید.
</MudAlert>
<MudButton Class="mt-2" Variant="Variant.Outlined" Color="Color.Primary"
StartIcon="@Icons.Material.Filled.Add"
OnClick="OpenAddAddressDialog">افزودن آدرس</MudButton>
<MudButton Class="mt-2" Variant="Variant.Outlined" Href="/profile/addresses">افزودن آدرس</MudButton>
}
else
{
@@ -42,25 +40,13 @@
<MudText Typo="Typo.subtitle2">@address.Title</MudText>
<MudText Typo="Typo.body2" Class="mud-text-secondary">@address.Address</MudText>
</MudStack>
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
@if (address.IsDefault)
{
<MudChip T="string" Color="Color.Success" Variant="Variant.Outlined" Size="Size.Small">پیش‌فرض</MudChip>
}
<span @onclick:stopPropagation="true">
<MudIconButton Icon="@Icons.Material.Filled.Edit"
Size="Size.Small"
Color="Color.Primary"
aria-label="ویرایش آدرس"
OnClick="@(() => OpenEditAddressDialog(address))" />
</span>
</MudStack>
@if (address.IsDefault)
{
<MudChip T="string" Color="Color.Success" Variant="Variant.Outlined">پیش‌فرض</MudChip>
}
</MudStack>
</MudPaper>
}
<MudButton Class="mt-2" Variant="Variant.Text" Color="Color.Primary"
StartIcon="@Icons.Material.Filled.Add"
OnClick="OpenAddAddressDialog">افزودن آدرس جدید</MudButton>
</MudStack>
}
</MudPaper>
@@ -1,6 +1,5 @@
using CMSMicroservice.Protobuf.Protos.UserAddress;
using CMSMicroservice.Protobuf.Protos.UserOrder;
using FrontOffice.Main.Pages.Profile.Components;
using FrontOffice.Main.Shared;
using FrontOffice.Main.Utilities;
using Microsoft.AspNetCore.Components;
@@ -36,11 +35,7 @@ public partial class CheckoutSummary : ComponentBase
await AuthDialogService.ShowAuthDialogAsync();
}
await Cart.EnsureInitializedAsync();
var userInfo = await AuthService.GetUserAuthInfo();
if (userInfo.HasAddress)
await LoadAddresses();
else
_addresses = new();
await LoadAddresses();
await LoadWalletBalance();
}
@@ -73,14 +68,12 @@ public partial class CheckoutSummary : ComponentBase
else
{
_addresses = new();
_selectedAddress = null;
}
}
catch (Exception ex)
{
Snackbar.Add($"خطا در بارگذاری آدرس‌ها: {ex.Message}", Severity.Error);
_addresses = new();
_selectedAddress = null;
}
finally
{
@@ -89,30 +82,6 @@ public partial class CheckoutSummary : ComponentBase
}
}
private async Task OpenAddAddressDialog()
{
var dialog = await DialogService.ShowAsync<AddAddressDialog>("افزودن آدرس جدید");
var result = await dialog.Result;
if (result is { Canceled: false })
{
await AuthService.RefreshTokenAsync();
await LoadAddresses();
}
}
private async Task OpenEditAddressDialog(CustomerAddressModel address)
{
var dialog = await DialogService.ShowAsync<EditAddressDialog>("ویرایش آدرس", new DialogParameters<EditAddressDialog>
{
{ x => x.Model, address }
});
var result = await dialog.Result;
if (result is { Canceled: false })
{
await LoadAddresses();
}
}
private async Task PlaceOrder()
{
if (!CanPlaceOrder || _selectedAddress is null)
@@ -112,6 +112,30 @@ else
</MudStack>
}
</MudStack>
@* نمایش PV سفارش *@
@if (_pvData != null && _pvData.TotalPv > 0)
{
<MudDivider Class="my-2" />
<MudStack Spacing="1" Class="pa-2" Style="background-color: var(--mud-palette-background-grey); border-radius: 8px;">
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
<MudIcon Icon="@Icons.Material.Filled.TrendingUp" Color="Color.Info" Size="Size.Small" />
<MudText Typo="Typo.subtitle2" Color="Color.Info">امتیاز فروش (PV)</MudText>
</MudStack>
@foreach (var product in _pvData.Products)
{
<MudStack Row="true" Justify="Justify.SpaceBetween">
<MudText Typo="Typo.caption" Class="mud-text-secondary">@product.ProductTitle × @product.Quantity</MudText>
<MudText Typo="Typo.caption">@string.Format("{0:N0}", product.TotalPv) PV</MudText>
</MudStack>
}
<MudDivider Class="my-1" />
<MudStack Row="true" Justify="Justify.SpaceBetween">
<MudText Typo="Typo.body2" Class="fw-bold">جمع PV:</MudText>
<MudText Typo="Typo.body2" Color="Color.Info" Class="fw-bold">@string.Format("{0:N0}", _pvData.TotalPv) PV</MudText>
</MudStack>
</MudStack>
}
</MudStack>
</MudPaper>
</MudContainer>
@@ -12,12 +12,17 @@ public partial class OrderDetail : ComponentBase
[Parameter] public long id { get; set; }
private GetUserOrderResponse? _order;
private CalculateOrderPVResponse? _pvData;
private bool _loading;
protected override async Task OnParametersSetAsync()
{
_loading = true;
_order = await OrderService.GetOrderAsync(id);
if (_order != null)
{
_pvData = await OrderService.CalculateOrderPVAsync(id);
}
_loading = false;
}
@@ -47,3 +52,4 @@ public partial class OrderDetail : ComponentBase
private static string GetProductImageUrl(string? imageUrl)
=> string.IsNullOrWhiteSpace(imageUrl) ? "/images/product-placeholder.svg" : imageUrl.TrimStart('/');
}
+3
View File
@@ -45,6 +45,9 @@
Href="https://wa.me/989123456789"
Target="_blank" />
</MudStack>
<MudStack Row="true" Spacing="2" Class="enamad">
<a referrerpolicy='origin' target='_blank' href='https://trustseal.enamad.ir/?id=688255&Code=uoMRGkyUYHAtMxkpBLB0KyPL5Ac66ACX'><img referrerpolicy='origin' src='https://trustseal.enamad.ir/logo.aspx?id=688255&Code=uoMRGkyUYHAtMxkpBLB0KyPL5Ac66ACX' alt='' style='cursor:pointer' code='uoMRGkyUYHAtMxkpBLB0KyPL5Ac66ACX'></a>
</MudStack>
</MudStack>
</MudItem>
@@ -79,7 +79,6 @@ public class AuthService
var isSignMainContractStr = claims.FirstOrDefault(c => c.key == "IsSignMainContract").value;
var hasPurchasedPackageStr = claims.FirstOrDefault(c => c.key == "HasPurchasedPackage").value;
var isClubMemberActiveStr = claims.FirstOrDefault(c => c.key == "IsClubMemberActive").value;
var hasAddressStr = claims.FirstOrDefault(c => c.key == "HasAddress").value;
_userAuthInfo.UserId = long.TryParse(userIdStr, out var userId) ? userId : 0;
_userAuthInfo.FirstName = firstName ?? string.Empty;
@@ -89,7 +88,6 @@ public class AuthService
_userAuthInfo.IsSignMainContract = bool.TryParse(isSignMainContractStr, out var isSignMainContract) && isSignMainContract;
_userAuthInfo.HasPurchasedPackage = bool.TryParse(hasPurchasedPackageStr, out var hasPurchased) && hasPurchased;
_userAuthInfo.IsClubMemberActive = bool.TryParse(isClubMemberActiveStr, out var isClubActive) && isClubActive;
_userAuthInfo.HasAddress = bool.TryParse(hasAddressStr, out var hasAddress) && hasAddress;
}
private async Task<List<(string key, string value)>?> GetTokenClaimsAsync()
@@ -26,18 +26,6 @@ public class NetworkNodeDto
/// نام پکیج موثر (مثلاً پکیج طلایی / نقره‌ای)
/// </summary>
public string? PackageName { get; set; }
/// <summary>آخرین امتیاز پای چپ پکیج طلایی</summary>
public int GoldLeftLegTotal { get; set; }
/// <summary>آخرین امتیاز پای راست پکیج طلایی</summary>
public int GoldRightLegTotal { get; set; }
/// <summary>آخرین امتیاز پای چپ پکیج نقره‌ای</summary>
public int SilverLeftLegTotal { get; set; }
/// <summary>آخرین امتیاز پای راست پکیج نقره‌ای</summary>
public int SilverRightLegTotal { get; set; }
}
/// <summary>
@@ -65,11 +53,6 @@ public class FlatNetworkNodeDto
/// نام پکیج موثر (مثلاً پکیج طلایی / نقره‌ای)
/// </summary>
public string? PackageName { get; set; }
public int GoldLeftLegTotal { get; set; }
public int GoldRightLegTotal { get; set; }
public int SilverLeftLegTotal { get; set; }
public int SilverRightLegTotal { get; set; }
}
/// <summary>
@@ -109,11 +92,7 @@ public class NetworkTreeDto
ActivationWeekNumber = node.ActivationWeekNumber,
JoinedAt = node.JoinedAt,
ReferralCode = node.ReferralCode,
PackageName = node.PackageName,
GoldLeftLegTotal = node.GoldLeftLegTotal,
GoldRightLegTotal = node.GoldRightLegTotal,
SilverLeftLegTotal = node.SilverLeftLegTotal,
SilverRightLegTotal = node.SilverRightLegTotal
PackageName = node.PackageName
};
result.Add(flatNode);
@@ -138,10 +138,6 @@ public class NetworkMembershipService
IsClubActive = node.IsClubActive,
ReferralCode = node.ReferralCode,
PackageName = string.IsNullOrWhiteSpace(node.PackageName) ? null : node.PackageName,
GoldLeftLegTotal = node.GoldLeftLegTotal,
GoldRightLegTotal = node.GoldRightLegTotal,
SilverLeftLegTotal = node.SilverLeftLegTotal,
SilverRightLegTotal = node.SilverRightLegTotal,
ActivationWeekNumber = node.ActivationWeekDefinitionId?.ToString(),
JoinedAt = node.JoinedAt?.ToDateTime(),
LeftChild = MapNodeFromProto(node.LeftChild),
@@ -97,4 +97,22 @@ public class OrderService
return 0.10;
}
}
/// <summary>
/// محاسبه PV سفارش
/// </summary>
public async Task<CalculateOrderPVResponse?> CalculateOrderPVAsync(long orderId)
{
try
{
return await _userOrderContractClient.CalculateOrderPVAsync(new CalculateOrderPVRequest
{
OrderId = orderId
});
}
catch
{
return null;
}
}
}
@@ -11,5 +11,4 @@ public class UserAuthInfo
public bool IsSignMainContract { get; set; }
public bool HasPurchasedPackage { get; set; }
public bool IsClubMemberActive { get; set; }
public bool HasAddress { get; set; }
}
@@ -1,5 +1,5 @@
{
"GwUrl": "https://cms.kbs1.ir",
"GwUrl": "https://cms.kbs2.ir",
"DownloadUrl": "",
"EncryptionSettings": {
"Key": "kmcQ3XTmH4mrdh8VHziuscyf8LLYjG//Kyni81nH/0E=",
@@ -14,5 +14,10 @@
"Microsoft.AspNetCore": "Warning"
}
},
"Seo": {
"SiteUrl": "https://kbs2.ir",
"SiteName": "کارابازار سلامت",
"DefaultDescription": "باشگاه مشتریان کارابازار سلامت (KBS) — فروشگاه محصولات سلامت، پاداش شفاف، کیف پول و شبکه فروش."
},
"AllowedHosts": "*"
}
+2 -18
View File
@@ -72,10 +72,10 @@
/* Compact Header */
.node-header-compact {
display: flex;
align-items: flex-start;
align-items: center;
gap: 6px;
padding: 6px 8px;
min-height: 100%;
height: 100%;
background: linear-gradient(135deg, #f8f9fa 0%, #fff 100%);
}
@@ -141,22 +141,6 @@
max-width: 120px;
}
/* D3: طلایی / نقره‌ای — پای چپ و راست */
.node-leg-scores {
margin-top: 3px;
line-height: 1.25;
font-size: 8px;
font-weight: 600;
}
.node-leg-scores .leg-score-line.gold {
color: #b8860b;
}
.node-leg-scores .leg-score-line.silver {
color: #607d8b;
}
/* Referral Code - only for club active members */
.node-referral {
display: flex;
+2 -13
View File
@@ -37,8 +37,8 @@ window.OrgChart = {
this.chart = new d3.OrgChart()
.container('#' + containerId)
.data(data)
.nodeWidth((d) => 150)
.nodeHeight((d) => 112)
.nodeWidth((d) => 140)
.nodeHeight((d) => 85)
.childrenMargin((d) => 50)
.compactMarginBetween((d) => 15)
.compactMarginPair((d) => 15)
@@ -80,16 +80,6 @@ window.OrgChart = {
</div>`
: '';
const gL = data.goldLeftLegTotal ?? 0;
const gR = data.goldRightLegTotal ?? 0;
const sL = data.silverLeftLegTotal ?? 0;
const sR = data.silverRightLegTotal ?? 0;
const legScoresHtml = `
<div class="node-leg-scores" title="آخرین امتیاز هفتگی پای چپ / راست">
<div class="leg-score-line gold">طلایی: چپ ${gL} · راست ${gR}</div>
<div class="leg-score-line silver">نقره‌ای: چپ ${sL} · راست ${sR}</div>
</div>`;
return `
<div class="org-node-card ${positionClass} ${activeClass} ${clubActiveClass}" data-user-id="${data.id}">
<div class="node-header-compact ${isRoot ? 'root' : ''}">
@@ -98,7 +88,6 @@ window.OrgChart = {
<div class="node-name-sm">${data.fullName || 'بدون نام'}</div>
<div class="node-level-sm">L${data.level || 0}${!isRoot ? ' • ' + (data.position === 'Left' ? 'چپ' : 'راست') : ''}</div>
${packageBadge}
${legScoresHtml}
${referralCodeHtml}
</div>
</div>