Compare commits

...

4 Commits

Author SHA1 Message Date
masoodafar-web 58037f0b49 Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Successful in 5m9s
2026-08-24 00:10:52 +03:30
masoodafar-web aae4b2693d fix(checkout): add missing JSInterop import for enhanced interactivity
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 3m47s
- Included the Microsoft.JSInterop namespace in the Checkout.razor.cs file to enable JavaScript interop functionality.
- This addition prepares the Checkout component for future enhancements that may require interaction with JavaScript libraries or functions.

These changes improve the potential for interactive features in the checkout process, enhancing user experience.
2026-08-23 23:46:35 +03:30
masoodafar-web 61fd805c75 feat(profile): enhance withdrawal request processing with IBAN normalization
Build and Deploy to Kubernetes / build-and-deploy (push) Failing after 4m8s
- Implemented a new method to normalize Iranian IBANs, ensuring they are formatted correctly before processing withdrawal requests.
- Added validation to provide user feedback if the IBAN format is invalid, improving the user experience during withdrawal submissions.
- Updated the withdrawal request logic to utilize the normalized IBAN, enhancing the reliability of the withdrawal process.

These changes improve the accuracy and usability of the withdrawal request feature, ensuring users can submit valid IBANs effectively.
2026-08-23 23:01:39 +03:30
masoodafar-web ef6d233bbc refactor(checkout): simplify address management and improve package selection logic
Build and Deploy to Kubernetes / build-and-deploy (push) Failing after 2m45s
- Removed the address selection UI and related logic from the Checkout page to streamline the user experience.
- Updated the payment validation to only require package selection, enhancing clarity for users.
- Adjusted the loading of addresses in related components based on user authentication status, ensuring addresses are only loaded when available.

These changes improve the overall usability of the checkout process by focusing on essential selections and reducing unnecessary complexity.
2026-08-23 22:32:47 +03:30
10 changed files with 65 additions and 158 deletions
+1 -1
View File
@@ -14,7 +14,7 @@
# User-specific files (MonoDevelop/Xamarin Studio) # User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs *.userprefs
# Mono auto generated files # Mono auto generated files
mono_crash.* mono_crash.*
# Build results # Build results
+1 -75
View File
@@ -71,80 +71,6 @@
</MudStack> </MudStack>
} }
</MudPaper> </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> </MudStack>
</MudItem> </MudItem>
@@ -241,7 +167,7 @@
@if (!CanProceedToPayment) @if (!CanProceedToPayment)
{ {
<MudText Typo="Typo.caption" Color="Color.Error" Align="Align.Center"> <MudText Typo="Typo.caption" Color="Color.Error" Align="Align.Center">
لطفاً پکیج و آدرس را انتخاب کنید. لطفاً پکیج را انتخاب کنید.
</MudText> </MudText>
} }
</MudStack> </MudStack>
+7 -72
View File
@@ -1,6 +1,4 @@
using CMSMicroservice.Protobuf.Protos.Package; using CMSMicroservice.Protobuf.Protos.Package;
using CMSMicroservice.Protobuf.Protos.UserAddress;
using FrontOffice.Main.Utilities; using FrontOffice.Main.Utilities;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop; using Microsoft.JSInterop;
@@ -13,14 +11,10 @@ public partial class Checkout
{ {
[Inject] private PackageService PackageService { get; set; } = default!; [Inject] private PackageService PackageService { get; set; } = default!;
[Inject] private PackageContract.PackageContractClient PackageClient { get; set; } = default!; [Inject] private PackageContract.PackageContractClient PackageClient { get; set; } = default!;
[Inject] private UserAddressContract.UserAddressContractClient UserAddressContract { get; set; } = default!;
[Parameter] public long? PackageId { get; set; } [Parameter] public long? PackageId { get; set; }
private Pack? _selectedPackage; private Pack? _selectedPackage;
private List<CustomerAddressModel> _addresses = new();
private CustomerAddressModel? _selectedAddress;
private bool _isLoadingAddresses;
private bool _isProcessingPayment; private bool _isProcessingPayment;
// Discount code // Discount code
@@ -31,16 +25,11 @@ public partial class Checkout
private long _discountAmount; private long _discountAmount;
private long _finalPrice; private long _finalPrice;
// Address management private bool CanProceedToPayment => _selectedPackage != null;
private bool _isSettingDefaultAddress;
private long? _settingDefaultAddressId;
private bool CanProceedToPayment => _selectedPackage != null && _selectedAddress != null;
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
await LoadPackageDetails(); await LoadPackageDetails();
await LoadAddresses();
} }
private async Task LoadPackageDetails() private async Task LoadPackageDetails()
@@ -72,60 +61,6 @@ 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() private async Task ApplyDiscountCode()
{ {
if (string.IsNullOrWhiteSpace(_discountCode)) if (string.IsNullOrWhiteSpace(_discountCode))
@@ -157,7 +92,7 @@ public partial class Checkout
_finalPrice = _selectedPackage!.Price; _finalPrice = _selectedPackage!.Price;
} }
} }
catch (Exception ex) catch (Exception)
{ {
_discountMessage = "خطا در اعمال کد تخفیف."; _discountMessage = "خطا در اعمال کد تخفیف.";
_discountApplied = false; _discountApplied = false;
@@ -175,9 +110,9 @@ public partial class Checkout
{ {
if (_isProcessingPayment) return; if (_isProcessingPayment) return;
if (!CanProceedToPayment || _selectedPackage == null || _selectedAddress == null) if (!CanProceedToPayment || _selectedPackage == null)
{ {
Snackbar.Add("لطفاً پکیج و آدرس را انتخاب کنید.", Severity.Warning); Snackbar.Add("لطفاً پکیج را انتخاب کنید.", Severity.Warning);
return; return;
} }
@@ -219,9 +154,9 @@ public partial class Checkout
private async Task DayaLoanPayment() private async Task DayaLoanPayment()
{ {
if (_selectedAddress == null) if (_selectedPackage == null)
{ {
Snackbar.Add("لطفاً آدرس را انتخاب کنید.", Severity.Warning); Snackbar.Add("لطفاً پکیج را انتخاب کنید.", Severity.Warning);
return; return;
} }
@@ -230,4 +165,4 @@ public partial class Checkout
} }
private record Pack(long Id, string Title, string Body, string Image, long Price, bool SupportsDirectPurchase = true, bool SupportsDayaPurchase = false); private record Pack(long Id, string Title, string Body, string Image, long Price, bool SupportsDirectPurchase = true, bool SupportsDayaPurchase = false);
} }
@@ -40,7 +40,11 @@ public partial class Checkout
} }
await VAT.LoadAsync(); await VAT.LoadAsync();
await DiscountCart.EnsureInitializedAsync(); await DiscountCart.EnsureInitializedAsync();
await LoadAddresses(); var userInfo = await AuthService.GetUserAuthInfo();
if (userInfo.HasAddress)
await LoadAddresses();
else
_addresses = new();
} }
private async Task LoadAddresses() private async Task LoadAddresses()
@@ -1,4 +1,4 @@
using CMSMicroservice.Protobuf.Protos.UserAddress; using FrontOffice.Main.Utilities;
using CMSMicroservice.Protobuf.Protos.UserAddress; using CMSMicroservice.Protobuf.Protos.UserAddress;
using FrontOffice.Main.Pages.Profile.Components; using FrontOffice.Main.Pages.Profile.Components;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
@@ -9,6 +9,7 @@ namespace FrontOffice.Main.Pages.Profile;
public partial class Addresses : ComponentBase public partial class Addresses : ComponentBase
{ {
[Inject] private UserAddressContract.UserAddressContractClient UserAddressContract { get; set; } = default!; [Inject] private UserAddressContract.UserAddressContractClient UserAddressContract { get; set; } = default!;
[Inject] private AuthService AuthService { get; set; } = default!;
private List<CustomerAddressModel> _addresses = new(); private List<CustomerAddressModel> _addresses = new();
private bool _isLoadingAddresses; private bool _isLoadingAddresses;
@@ -42,7 +43,10 @@ public partial class Addresses : ComponentBase
var dialog = await DialogService.ShowAsync<AddAddressDialog>("افزودن آدرس جدید"); var dialog = await DialogService.ShowAsync<AddAddressDialog>("افزودن آدرس جدید");
var result = await dialog.Result; var result = await dialog.Result;
if (result is not null && !result.Canceled) if (result is not null && !result.Canceled)
{
await LoadAddresses(); await LoadAddresses();
await AuthService.RefreshTokenAsync();
}
} }
private async Task OpenEditAddressDialog(CustomerAddressModel address) private async Task OpenEditAddressDialog(CustomerAddressModel address)
@@ -80,6 +84,7 @@ public partial class Addresses : ComponentBase
await UserAddressContract.DeleteCustomerAddressAsync(new() { Id = id }); await UserAddressContract.DeleteCustomerAddressAsync(new() { Id = id });
Snackbar.Add("آدرس حذف شد.", Severity.Success); Snackbar.Add("آدرس حذف شد.", Severity.Success);
await LoadAddresses(); await LoadAddresses();
await AuthService.RefreshTokenAsync();
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -333,6 +333,7 @@ public partial class Index
if (!result.Canceled) if (!result.Canceled)
{ {
await LoadAddresses(); await LoadAddresses();
await AuthService.RefreshTokenAsync();
} }
} }
@@ -385,6 +386,7 @@ public partial class Index
}); });
Snackbar.Add("آدرس با موفقیت حذف شد.", Severity.Success); Snackbar.Add("آدرس با موفقیت حذف شد.", Severity.Success);
await LoadAddresses(); await LoadAddresses();
await AuthService.RefreshTokenAsync();
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -491,11 +493,6 @@ public partial class Index
Snackbar.Add("لطفا اطلاعات شخصی خود را تکمیل کنید. (تاریخ تولد وارد نشده)", Severity.Error); Snackbar.Add("لطفا اطلاعات شخصی خود را تکمیل کنید. (تاریخ تولد وارد نشده)", Severity.Error);
return; return;
} }
if (!_addresses.Any())
{
Snackbar.Add("آدرس محل سکونت شما الزامی است!", Severity.Error);
return;
}
var url = "https://dayadiamond.ir/profile/creditpurchase/?merchantcode=56146364"; var url = "https://dayadiamond.ir/profile/creditpurchase/?merchantcode=56146364";
await JSRuntime.InvokeVoidAsync("open", url, "_blank"); await JSRuntime.InvokeVoidAsync("open", url, "_blank");
@@ -58,8 +58,19 @@ public partial class WithdrawalRequests : ComponentBase
try try
{ {
_isSubmittingWithdrawal = true; _isSubmittingWithdrawal = true;
_withdrawIban=_withdrawIban!.Trim().ToUpper().Replace("IR", "").Replace(" ", "");
await WalletService.RequestWithdrawalAsync(_selectedPayout.Id, _withdrawMethod, "IR"+_withdrawIban); string? normalizedIban = null;
if (_withdrawMethod == WithdrawalMethodClient.Cash)
{
normalizedIban = NormalizeIranianIban(_withdrawIban);
if (normalizedIban is null)
{
Snackbar.Add("فرمت شماره شبا معتبر نیست. باید IR و ۲۴ رقم باشد (خط تیره اختیاری است).", Severity.Warning);
return;
}
}
await WalletService.RequestWithdrawalAsync(_selectedPayout.Id, _withdrawMethod, normalizedIban);
Snackbar.Add("درخواست برداشت ثبت شد.", Severity.Success); Snackbar.Add("درخواست برداشت ثبت شد.", Severity.Success);
// بروزرسانی لیست // بروزرسانی لیست
@@ -125,4 +136,26 @@ public partial class WithdrawalRequests : ComponentBase
1 => "الماس", 1 => "الماس",
_ => "-" _ => "-"
}; };
/// <summary>
/// فاصله و خط تیره را حذف می‌کند و شبا را به شکل IR + ۲۴ رقم برمی‌گرداند.
/// </summary>
private static string? NormalizeIranianIban(string? iban)
{
if (string.IsNullOrWhiteSpace(iban))
return null;
var normalized = iban.Trim().ToUpperInvariant()
.Replace(" ", "", StringComparison.Ordinal)
.Replace("-", "", StringComparison.Ordinal);
if (normalized.StartsWith("IR", StringComparison.Ordinal))
normalized = normalized[2..];
normalized = "IR" + normalized;
return System.Text.RegularExpressions.Regex.IsMatch(normalized, @"^IR\d{24}$")
? normalized
: null;
}
} }
@@ -35,7 +35,11 @@ public partial class CheckoutSummary : ComponentBase
await AuthDialogService.ShowAuthDialogAsync(); await AuthDialogService.ShowAuthDialogAsync();
} }
await Cart.EnsureInitializedAsync(); await Cart.EnsureInitializedAsync();
await LoadAddresses(); var userInfo = await AuthService.GetUserAuthInfo();
if (userInfo.HasAddress)
await LoadAddresses();
else
_addresses = new();
await LoadWalletBalance(); await LoadWalletBalance();
} }
@@ -79,6 +79,7 @@ public class AuthService
var isSignMainContractStr = claims.FirstOrDefault(c => c.key == "IsSignMainContract").value; var isSignMainContractStr = claims.FirstOrDefault(c => c.key == "IsSignMainContract").value;
var hasPurchasedPackageStr = claims.FirstOrDefault(c => c.key == "HasPurchasedPackage").value; var hasPurchasedPackageStr = claims.FirstOrDefault(c => c.key == "HasPurchasedPackage").value;
var isClubMemberActiveStr = claims.FirstOrDefault(c => c.key == "IsClubMemberActive").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.UserId = long.TryParse(userIdStr, out var userId) ? userId : 0;
_userAuthInfo.FirstName = firstName ?? string.Empty; _userAuthInfo.FirstName = firstName ?? string.Empty;
@@ -88,6 +89,7 @@ public class AuthService
_userAuthInfo.IsSignMainContract = bool.TryParse(isSignMainContractStr, out var isSignMainContract) && isSignMainContract; _userAuthInfo.IsSignMainContract = bool.TryParse(isSignMainContractStr, out var isSignMainContract) && isSignMainContract;
_userAuthInfo.HasPurchasedPackage = bool.TryParse(hasPurchasedPackageStr, out var hasPurchased) && hasPurchased; _userAuthInfo.HasPurchasedPackage = bool.TryParse(hasPurchasedPackageStr, out var hasPurchased) && hasPurchased;
_userAuthInfo.IsClubMemberActive = bool.TryParse(isClubMemberActiveStr, out var isClubActive) && isClubActive; _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() private async Task<List<(string key, string value)>?> GetTokenClaimsAsync()
@@ -11,4 +11,5 @@ public class UserAuthInfo
public bool IsSignMainContract { get; set; } public bool IsSignMainContract { get; set; }
public bool HasPurchasedPackage { get; set; } public bool HasPurchasedPackage { get; set; }
public bool IsClubMemberActive { get; set; } public bool IsClubMemberActive { get; set; }
public bool HasAddress { get; set; }
} }