a3681a8bcd
- حذف متن hardcoded 'پکیج پایه ۵۶ میلیون تومان' و دیالوگ inline - ایجاد PackagePurchaseDialog — کامپوننت مستقل 2 مرحلهای: مرحله ۱: نمایش کاشیهای پکیج (ریسپانسیو ۲-۳ تایی) مرحله ۲: انتخاب روش پرداخت (درگاه بانکی / دایا الماس) - دایا الماس فقط برای پکیج پایه + دور اول نمایش داده میشه - پکیجها داینامیک از API بارگذاری میشن - تغییر متن CTA از 'شروع فرآیند تامین اعتبار' به 'تهیه پکیج' - تغییر متن راهنما از 'پکیج پایه را تهیه کنید' به 'یکی از پکیجها را تهیه کنید' - CSS: hover effects + badge + responsive tiles
507 lines
18 KiB
C#
507 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 callbackUrl = $"{Navigation.BaseUri}profile/payment-callback";
|
|
|
|
var response = await PackageContract.CustomerPurchasePackageAsync(new CustomerPurchasePackageRequest
|
|
{
|
|
PackageId = packageId,
|
|
CallbackUrl = callbackUrl,
|
|
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");
|
|
}
|
|
}
|