Files
FrontOffice/src/FrontOffice.Main/Pages/PackageDetail.razor.cs
T
masoodafar-web d71d463668 Phase 8c: FO Package pages — Customer RPCs + dynamic features
- NuGet: 0.0.182 → 0.0.185 + local feed source
- PackageDetail: GetPackageAsync → GetCustomerPackageDetailsAsync
  Features & specs now driven by API (PackageFeature) not hardcoded
- Packages.razor: Un-excluded from build, dynamic feature bullets
  from CustomerPackageModel (DiscountMultiplier, MagicWallet, etc.)
- PackageService: Enriched PackageDto with 8 new package fields
  GetUserPackageStatusAsync connected to real RPC (was stub)
2026-02-26 17:52:37 +03:30

291 lines
8.7 KiB
C#

using Blazored.LocalStorage;
using CMSMicroservice.Protobuf.Protos.Package;
using FrontOffice.Main.Shared;
using FrontOffice.Main.Utilities;
using Grpc.Core;
using Microsoft.AspNetCore.Components;
using MudBlazor;
namespace FrontOffice.Main.Pages;
public partial class PackageDetail : IDisposable
{
[Parameter] public long Id { get; set; }
[Inject] private PackageContract.PackageContractClient PackageClient { get; set; } = default!;
[Inject] private ILocalStorageService LocalStorage { get; set; } = default!;
private PackageDetailDto? _package;
private List<Review> _reviews = new();
private List<RelatedPackage> _relatedPackages = new();
private bool _isLoading = true;
private bool _isPurchasing;
private bool _isAuthenticated;
private CancellationTokenSource? _loadCts;
// Comment form
private NewComment _newComment = new();
private bool _isSubmittingComment;
private List<BreadcrumbItem> _breadcrumbItems = new()
{
new BreadcrumbItem("صفحه اصلی", RouteConstants.Main.MainPage),
new BreadcrumbItem("پکیج‌ها", "#features"),
new BreadcrumbItem("جزئیات پکیج", null, disabled: true)
};
protected override async Task OnInitializedAsync()
{
await CheckAuthStatus();
if (Id < 1)
{
_isLoading = false;
return;
}
await LoadPackageDetailsAsync();
}
protected override async Task OnParametersSetAsync()
{
if (Id > 0)
{
await LoadPackageDetailsAsync();
}
}
private async Task LoadPackageDetailsAsync()
{
_isLoading = true;
_loadCts?.Cancel();
_loadCts?.Dispose();
_loadCts = new CancellationTokenSource();
try
{
// Load package details via Customer RPC
var response = await PackageClient.GetCustomerPackageDetailsAsync(
new GetCustomerPackageDetailsRequest { PackageId = Id },
cancellationToken: _loadCts.Token);
if (response != null)
{
// Build features from API response
var features = response.Features?.Select(f => f.Title).ToList() ?? new List<string>();
// Build specifications from API features (highlighted ones)
var specs = response.Features?
.Where(f => f.IsHighlighted)
.Select(f => new Specification
{
Name = f.Title,
Value = f.Description,
Icon = string.IsNullOrEmpty(f.Icon) ? Icons.Material.Filled.Star : f.Icon
}).ToList() ?? new List<Specification>();
_package = new PackageDetailDto
{
Id = response.Id,
Title = response.Title,
Body = response.Description,
Image = response.ImagePath ?? string.Empty,
Specifications = specs,
Features = features,
Pricing = new PricingInfo
{
OriginalPrice = response.Price,
FinalPrice = response.Price,
HasDiscount = false,
DiscountPercent = 0
}
};
// Load reviews (mock data for now)
await LoadReviewsAsync();
// Load related packages
await LoadRelatedPackagesAsync();
}
}
catch (RpcException rpcEx)
{
Snackbar.Add($"خطا در بارگذاری پکیج: {rpcEx.Status.Detail}", Severity.Error);
}
catch (Exception ex)
{
Snackbar.Add($"خطا در بارگذاری پکیج: {ex.Message}", Severity.Error);
}
finally
{
_isLoading = false;
await InvokeAsync(StateHasChanged);
}
}
private async Task LoadReviewsAsync()
{
// TODO: Load reviews from API when endpoint is available
_reviews = new List<Review>();
}
private async Task LoadRelatedPackagesAsync()
{
// TODO: Load related packages from API when endpoint is available
_relatedPackages = new List<RelatedPackage>();
}
private async Task PurchasePackage()
{
if (_package == null) return;
_isPurchasing = true;
try
{
// Navigate to checkout page with package ID
Navigation.NavigateTo($"{RouteConstants.Checkout.Index}/{_package.Id}");
}
catch (Exception ex)
{
Snackbar.Add($"خطا در انتقال به صفحه پرداخت: {ex.Message}", Severity.Error);
}
finally
{
_isPurchasing = false;
await InvokeAsync(StateHasChanged);
}
}
private async Task CheckAuthStatus()
{
var token = await LocalStorage.GetItemAsync<string>("auth:token");
_isAuthenticated = !string.IsNullOrWhiteSpace(token);
}
private async Task SubmitComment()
{
if (!_isAuthenticated)
{
Snackbar.Add("برای ثبت نظر ابتدا وارد حساب کاربری شوید.", Severity.Warning);
return;
}
if (string.IsNullOrWhiteSpace(_newComment.Comment))
{
Snackbar.Add("لطفاً نظر خود را وارد کنید.", Severity.Warning);
return;
}
if (_newComment.Rating < 1 || _newComment.Rating > 5)
{
Snackbar.Add("لطفاً امتیاز را انتخاب کنید.", Severity.Warning);
return;
}
_isSubmittingComment = true;
try
{
// TODO: Submit comment to API
await Task.Delay(1000); // Simulate API call
// Add the new comment to the list
var newReview = new Review
{
UserName = "کاربر فعلی", // TODO: Get from user profile
Rating = _newComment.Rating,
Comment = _newComment.Comment,
Date = DateTime.Now.ToString("yyyy/MM/dd")
};
_reviews.Insert(0, newReview);
_newComment = new NewComment();
Snackbar.Add("نظر شما با موفقیت ثبت شد.", Severity.Success);
}
catch (Exception ex)
{
Snackbar.Add($"خطا در ثبت نظر: {ex.Message}", Severity.Error);
}
finally
{
_isSubmittingComment = false;
await InvokeAsync(StateHasChanged);
}
}
private void NavigateToPackage(string packageId)
{
Navigation.NavigateTo($"{RouteConstants.Package.Detail}/{packageId}");
}
private async Task OpenAuthDialog()
{
var dialog = await DialogService.ShowAsync<AuthDialog>("ورود به حساب کاربری");
var result = await dialog.Result;
if (!result.Canceled)
{
// User logged in successfully, refresh the page to show comment form
await CheckAuthStatus();
await InvokeAsync(StateHasChanged);
}
}
public void Dispose()
{
_loadCts?.Cancel();
_loadCts?.Dispose();
_loadCts = null;
}
public class PackageDetailDto
{
public long? Id { get; set; }
public string? Title { get; set; }
public string? Body { get; set; }
public string? Image { get; set; }
public List<Specification> Specifications { get; set; } = new();
public List<string> Features { get; set; } = new();
public PricingInfo Pricing { get; set; } = new();
}
public class Specification
{
public string? Name { get; set; }
public string? Value { get; set; }
public string? Icon { get; set; }
}
public class PricingInfo
{
public long OriginalPrice { get; set; }
public long FinalPrice { get; set; }
public bool HasDiscount { get; set; }
public int DiscountPercent { get; set; }
}
public class Review
{
public string? UserName { get; set; }
public int Rating { get; set; }
public string? Comment { get; set; }
public string? Date { get; set; }
}
public class RelatedPackage
{
public string? Id { get; set; }
public string? Title { get; set; }
public string? ShortDescription { get; set; }
public string? Image { get; set; }
public PricingInfo Pricing { get; set; } = new();
}
public class NewComment
{
public int Rating { get; set; } = 5;
public string? Comment { get; set; }
}
}