d8289237e9
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 5m46s
Replaced instances of "شجرهنامه" with "سازمان فروش" across multiple files to ensure consistent terminology. Additionally, updated references to "پاداش تیمی" to "پاداش های دریافتی" for clarity in user-facing text. Affected files: - FAQ.razor.cs - Index.razor - Index.razor.cs - Profile/Index.razor - Profile/Index.razor.cs - Profile/MagicWallet.razor - Profile/Tree.razor - Profile/Wallet.razor - Utilities/WalletService.cs
504 lines
18 KiB
C#
504 lines
18 KiB
C#
using FluentValidation;
|
|
|
|
using FrontOffice.Main.Pages.Profile.Components;
|
|
using FrontOffice.Main.Utilities;
|
|
using Mapster;
|
|
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;
|
|
|
|
public partial class Index
|
|
{
|
|
[Inject] private UserContract.UserContractClient UserContract { get; set; } = default!;
|
|
[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 GetUserForCustomerResponse _userProfile = new();
|
|
private BlogPostCardDto? _latestPost;
|
|
private UpdateCustomerProfileRequest _updateUserRequest = new();
|
|
|
|
private MudForm? _personalForm;
|
|
|
|
private bool _isPersonalSaving;
|
|
private bool _isSettingsSaving;
|
|
|
|
private string _copyMessage = string.Empty;
|
|
|
|
private DateTime? _date;
|
|
|
|
// Referral Link Control
|
|
private bool _hasPurchasedPackage;
|
|
private bool _isClubMemberActive;
|
|
private bool CanShowReferralLink => _hasPurchasedPackage && _isClubMemberActive;
|
|
|
|
// Purchase flow
|
|
private bool _isProcessingPayment;
|
|
private int _purchaseCycleCount; // تعداد دورهای خرید — اگر > 0 فقط IPG مجاز
|
|
|
|
// Dashboard Tiles
|
|
private readonly List<DashTile> _dashTiles = new()
|
|
{
|
|
new(RouteConstants.Profile.Personal, Icons.Material.Filled.Person, "اطلاعات شخصی", "نمایش و ویرایش", "background:rgba(99,102,241,.12); color:#6366f1;"),
|
|
new(RouteConstants.Profile.Addresses, Icons.Material.Filled.LocationOn, "آدرسها", "مدیریت آدرسها", "background:rgba(99,102,241,.12); color:#6366f1;"),
|
|
new(RouteConstants.Profile.Tree, Icons.Material.Filled.AccountTree, "سازمان فروش", "ساختار تیم", "background:rgba(99,102,241,.12); color:#6366f1;"),
|
|
new(RouteConstants.Gateway.StoreChooser, Icons.Material.Filled.Store, "فروشگاهها", "ورود به فروشگاه", "background:rgba(16,185,129,.12); color:#10b981;"),
|
|
new(RouteConstants.Profile.Wallet, Icons.Material.Filled.AccountBalanceWallet, "کیف پول", "مدیریت و تاریخچه", "background:rgba(16,185,129,.12); color:#10b981;"),
|
|
new(RouteConstants.Profile.MagicWallet, Icons.Material.Filled.AutoAwesome, "کیفپول جادویی", "شارژ چندبرابری", "background:rgba(168,85,247,.12); color:#a855f7;"),
|
|
new(RouteConstants.Profile.WithdrawalRequests, Icons.Material.Filled.RequestPage, "درخواست برداشت", "ثبت و پیگیری", "background:rgba(245,158,11,.12); color:#f59e0b;"),
|
|
new(RouteConstants.Club.Membership, Icons.Material.Filled.CardMembership, "باشگاه مشتریان", "عضویت و مزایا", "background:rgba(168,85,247,.12); color:#a855f7;"),
|
|
new(RouteConstants.Network.Statistics, Icons.Material.Filled.Groups, "آمار باشگاه", "جزئیات باشگاه", "background:rgba(59,130,246,.12); color:#3b82f6;"),
|
|
new(RouteConstants.Commission.Dashboard, Icons.Material.Filled.Payments, "پاداش", "درآمد و تاریخچه", "background:rgba(16,185,129,.12); color:#10b981;"),
|
|
};
|
|
|
|
private record DashTile(string Href, string Icon, string Title, string Subtitle, string AvatarStyle);
|
|
|
|
// Address management
|
|
private List<CustomerAddressModel> _addresses = new();
|
|
private bool _isLoadingAddresses;
|
|
private CreateNewUserAddressRequest _newAddressRequest = new();
|
|
private UpdateUserAddressRequest _editAddressRequest = new();
|
|
private MudForm? _addressForm;
|
|
private readonly CreateNewUserAddressRequestValidator _addressValidator = new();
|
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
|
{
|
|
await base.OnAfterRenderAsync(firstRender);
|
|
if (firstRender)
|
|
{
|
|
// Refresh token to get latest user claims
|
|
await AuthService.RefreshTokenAsync();
|
|
|
|
await LoadUserAuthInfo();
|
|
await LoadUserProfile();
|
|
await LoadAddresses();
|
|
await LoadWallet();
|
|
await LoadLatestPost();
|
|
|
|
// نمایش Modal قرارداد باشگاه اگر پکیج خریده ولی باشگاه فعال نشده
|
|
await CheckAndShowClubContractModal();
|
|
}
|
|
}
|
|
|
|
private async Task LoadUserAuthInfo()
|
|
{
|
|
try
|
|
{
|
|
var userInfo = await AuthService.GetUserAuthInfo();
|
|
_hasPurchasedPackage = userInfo.HasPurchasedPackage;
|
|
_isClubMemberActive = userInfo.IsClubMemberActive;
|
|
}
|
|
catch
|
|
{
|
|
_hasPurchasedPackage = false;
|
|
_isClubMemberActive = false;
|
|
}
|
|
StateHasChanged();
|
|
}
|
|
|
|
/// <summary>
|
|
/// بررسی و نمایش Modal قرارداد باشگاه مشتریان
|
|
/// اگر کاربر پکیج خریده ولی هنوز قرارداد باشگاه را امضا نکرده
|
|
/// </summary>
|
|
private async Task CheckAndShowClubContractModal()
|
|
{
|
|
// اگر پکیج خریده ولی باشگاه فعال نیست = باید قرارداد امضا کند
|
|
if (_hasPurchasedPackage && !_isClubMemberActive)
|
|
{
|
|
var options = new DialogOptions
|
|
{
|
|
BackdropClick = false, // غیرقابل بسته شدن با کلیک بیرون
|
|
CloseOnEscapeKey = false, // غیرقابل بسته شدن با Escape
|
|
CloseButton = false, // بدون دکمه بستن
|
|
MaxWidth = MaxWidth.Medium,
|
|
FullWidth = true
|
|
};
|
|
|
|
var dialog = await DialogService.ShowAsync<Shared.ClubMembershipContractDialog>(
|
|
"قرارداد باشگاه مشتریان",
|
|
options);
|
|
|
|
var result = await dialog.Result;
|
|
|
|
// اگر موفق بود، صفحه را refresh کنیم
|
|
if (!result.Canceled)
|
|
{
|
|
await LoadUserAuthInfo();
|
|
StateHasChanged();
|
|
}
|
|
}
|
|
}
|
|
|
|
private string _walletCredit = "-";
|
|
private string _walletDiscount = "-";
|
|
private string _walletNetwork = "-";
|
|
private async Task LoadWallet()
|
|
{
|
|
var b = await WalletService.GetBalancesAsync();
|
|
_walletCredit = FormatPrice(b.CreditBalance);
|
|
var discount = FormatPrice(b.DiscountBalance);
|
|
_walletNetwork = FormatPrice(b.NetworkBalance);
|
|
_walletDiscount = discount;
|
|
|
|
// بارگذاری تعداد دورهای خرید برای کنترل روشهای پرداخت
|
|
var magicStatus = await WalletService.GetMagicWalletStatusAsync();
|
|
_purchaseCycleCount = magicStatus.PurchaseCycleCount;
|
|
|
|
StateHasChanged();
|
|
}
|
|
|
|
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.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 GetUserForCustomerResponse();
|
|
}
|
|
StateHasChanged();
|
|
}
|
|
|
|
private async Task SavePersonalInfo()
|
|
{
|
|
if (_personalForm is null) return;
|
|
|
|
await _personalForm.Validate();
|
|
if (!_personalForm.IsValid) return;
|
|
|
|
_isPersonalSaving = true;
|
|
|
|
try
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(_updateUserRequest.NationalCode))
|
|
_updateUserRequest.NationalCode = _updateUserRequest.NationalCode.PersianToEnglish();
|
|
|
|
if (_date != null)
|
|
_updateUserRequest.BirthDate = _date.Value.DateTimeToTimestamp();
|
|
|
|
await UserContract.UpdateCustomerProfileAsync(request: _updateUserRequest);
|
|
await LoadUserProfile();
|
|
Snackbar.Add("اطلاعات شخصی با موفقیت ذخیره شد.", Severity.Success);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Snackbar.Add($"خطا در ذخیره اطلاعات: {ex.Message}", Severity.Error);
|
|
}
|
|
finally
|
|
{
|
|
_isPersonalSaving = false;
|
|
await InvokeAsync(StateHasChanged);
|
|
}
|
|
}
|
|
|
|
|
|
private async Task SaveSettings()
|
|
{
|
|
_isSettingsSaving = true;
|
|
|
|
try
|
|
{
|
|
await SavePersonalInfo();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Snackbar.Add($"خطا در ذخیره تنظیمات: {ex.Message}", Severity.Error);
|
|
}
|
|
finally
|
|
{
|
|
_isSettingsSaving = false;
|
|
await InvokeAsync(StateHasChanged);
|
|
}
|
|
}
|
|
|
|
private async Task CopyReferralCode()
|
|
{
|
|
try
|
|
{
|
|
await JSRuntime.InvokeVoidAsync("navigator.clipboard.writeText", $"{Navigation.BaseUri}?ref={_userProfile.ReferralCode}");
|
|
_copyMessage = "کد دعوت کپی شد!";
|
|
Snackbar.Add("کد دعوت در کلیپبورد کپی شد.", Severity.Success);
|
|
|
|
// Clear message after 3 seconds
|
|
await Task.Delay(3000);
|
|
_copyMessage = string.Empty;
|
|
await InvokeAsync(StateHasChanged);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Snackbar.Add("خطا در کپی کردن کد دعوت.", Severity.Error);
|
|
}
|
|
}
|
|
|
|
private async Task ShareReferralCode()
|
|
{
|
|
var shareText = $"کد دعوت من در کارا بازار سلامت: {_userProfile.ReferralCode}\nبرای عضویت از این لینک استفاده کنید:";
|
|
|
|
try
|
|
{
|
|
// Try to use Web Share API if available
|
|
await JSRuntime.InvokeVoidAsync("navigator.share",
|
|
new
|
|
{
|
|
title = "کد دعوت کارا بازار سلامت",
|
|
text = shareText,
|
|
url = $"{Navigation.BaseUri}?ref={_userProfile.ReferralCode}"
|
|
});
|
|
}
|
|
catch
|
|
{
|
|
// Fallback: copy to clipboard
|
|
await JSRuntime.InvokeVoidAsync("navigator.clipboard.writeText", $"{Navigation.BaseUri}?ref={_userProfile.ReferralCode}");
|
|
Snackbar.Add("لینک دعوت در کلیپبورد کپی شد.", Severity.Success);
|
|
}
|
|
}
|
|
|
|
// Address management methods
|
|
private async Task LoadAddresses()
|
|
{
|
|
_isLoadingAddresses = true;
|
|
try
|
|
{
|
|
var response = await UserAddressContract.GetCustomerAddressesAsync(request: new());
|
|
if (response?.Models?.Any() == true)
|
|
{
|
|
_addresses = response.Models.ToList();
|
|
}
|
|
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 OpenAddAddressDialog()
|
|
{
|
|
var dialog = await DialogService.ShowAsync<AddAddressDialog>("افزودن آدرس جدید");
|
|
var result = await dialog.Result;
|
|
|
|
if (!result.Canceled)
|
|
{
|
|
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.Canceled)
|
|
{
|
|
await LoadAddresses();
|
|
}
|
|
}
|
|
|
|
private async Task SetAsDefaultAddress(long addressId)
|
|
{
|
|
try
|
|
{
|
|
await UserAddressContract.SetAddressAsDefaultAsync(request: new()
|
|
{
|
|
Id = addressId
|
|
});
|
|
Snackbar.Add("آدرس پیشفرض با موفقیت تغییر کرد.", Severity.Success);
|
|
await LoadAddresses();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Snackbar.Add($"خطا در تغییر آدرس پیشفرض: {ex.Message}", Severity.Error);
|
|
}
|
|
}
|
|
|
|
private async Task DeleteAddress(long addressId)
|
|
{
|
|
var result = await DialogService.ShowMessageBox(
|
|
"تأیید حذف",
|
|
"آیا از حذف این آدرس اطمینان دارید؟",
|
|
yesText: "حذف",
|
|
cancelText: "لغو");
|
|
|
|
if (result == true)
|
|
{
|
|
try
|
|
{
|
|
await UserAddressContract.DeleteUserAddressAsync(request: new()
|
|
{
|
|
Id = addressId
|
|
});
|
|
Snackbar.Add("آدرس با موفقیت حذف شد.", Severity.Success);
|
|
await LoadAddresses();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Snackbar.Add($"خطا در حذف آدرس: {ex.Message}", Severity.Error);
|
|
}
|
|
}
|
|
}
|
|
|
|
private async Task OpenPurchaseOptions()
|
|
{
|
|
var options = new DialogOptions
|
|
{
|
|
MaxWidth = MaxWidth.Small,
|
|
FullWidth = true,
|
|
CloseOnEscapeKey = true,
|
|
BackdropClick = true
|
|
};
|
|
|
|
var parameters = new DialogParameters<Shared.PackagePurchaseDialog>
|
|
{
|
|
{ x => x.PurchaseCycleCount, _purchaseCycleCount }
|
|
};
|
|
|
|
var dialog = await DialogService.ShowAsync<Shared.PackagePurchaseDialog>(
|
|
string.Empty, parameters, options);
|
|
var result = await dialog.Result;
|
|
|
|
if (result.Canceled) return;
|
|
|
|
if (result.Data is Shared.PackagePurchaseDialog.PackagePurchaseResult purchase)
|
|
{
|
|
switch (purchase.Method)
|
|
{
|
|
case Shared.PackagePurchaseDialog.PaymentMethodType.DirectPayment:
|
|
await ProcessDirectPayment(purchase.Package.Id);
|
|
break;
|
|
case Shared.PackagePurchaseDialog.PaymentMethodType.DayaLoan:
|
|
await ProcessDayaLoanPayment();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private async Task ProcessDirectPayment(long packageId)
|
|
{
|
|
if (_isProcessingPayment) return;
|
|
|
|
_isProcessingPayment = true;
|
|
StateHasChanged();
|
|
|
|
try
|
|
{
|
|
var response = await PackageContract.CustomerPurchasePackageAsync(new CustomerPurchasePackageRequest
|
|
{
|
|
PackageId = packageId,
|
|
PurchaseMethod = PurchaseMethodEnum.PurchaseMethodGateway
|
|
});
|
|
|
|
if (!response.Success)
|
|
{
|
|
Snackbar.Add(response.Message ?? "خطا در آغاز فرآیند پرداخت", Severity.Error);
|
|
return;
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(response.PaymentGatewayUrl))
|
|
{
|
|
Navigation.NavigateTo(response.PaymentGatewayUrl, forceLoad: true);
|
|
}
|
|
else
|
|
{
|
|
Snackbar.Add("خطا در دریافت آدرس درگاه پرداخت", Severity.Error);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Snackbar.Add($"خطا در فرآیند پرداخت: {ex.Message}", Severity.Error);
|
|
}
|
|
finally
|
|
{
|
|
_isProcessingPayment = false;
|
|
await InvokeAsync(StateHasChanged);
|
|
}
|
|
}
|
|
|
|
private async Task ProcessDayaLoanPayment()
|
|
{
|
|
if (string.IsNullOrWhiteSpace(_updateUserRequest.FirstName))
|
|
{
|
|
Snackbar.Add("لطفا اطلاعات شخصی خود را تکمیل کنید. (نام وارد نشده)", Severity.Error);
|
|
return;
|
|
}
|
|
if (string.IsNullOrWhiteSpace(_updateUserRequest.LastName))
|
|
{
|
|
Snackbar.Add("لطفا اطلاعات شخصی خود را تکمیل کنید. (نام خانوادگی وارد نشده)", Severity.Error);
|
|
return;
|
|
}
|
|
if (string.IsNullOrWhiteSpace(_updateUserRequest.NationalCode))
|
|
{
|
|
Snackbar.Add("لطفا اطلاعات شخصی خود را تکمیل کنید. (کدملی وارد نشده)", Severity.Error);
|
|
return;
|
|
}
|
|
if (_updateUserRequest.BirthDate == null)
|
|
{
|
|
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");
|
|
}
|
|
}
|