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)
This commit is contained in:
@@ -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.182" />
|
||||
<PackageReference Include="Foursat.CMSMicroservice.Protobuf" Version="0.0.185" />
|
||||
<!-- <ProjectReference Include="../../../CMS/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj" />-->
|
||||
<PackageReference Include="MudBlazor" Version="8.14.0" />
|
||||
<PackageReference Include="Blazored.LocalStorage" Version="4.5.0" />
|
||||
@@ -56,12 +56,6 @@
|
||||
<Content Include="..\.dockerignore">
|
||||
<Link>.dockerignore</Link>
|
||||
</Content>
|
||||
<Content Remove="Pages\Package\Packages.razor" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="Pages\Package\Packages.razor.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
<configuration>
|
||||
<packageSources>
|
||||
<clear />
|
||||
<!-- Local packages (dev builds) -->
|
||||
<add key="Local" value="/home/masoud/Apps/project/FourSat/nupkg" />
|
||||
<!-- Nexus (hosts FourSat packages + proxies nuget.org) -->
|
||||
<add key="Nexus" value="http://194.5.195.53:32081/repository/nuget-all/index.json" allowInsecureConnections="true" />
|
||||
</packageSources>
|
||||
|
||||
@@ -64,20 +64,43 @@
|
||||
<MudText Typo="Typo.h6" Color="Color.Success">@package.FormattedPrice</MudText>
|
||||
</MudStack>
|
||||
|
||||
<!-- Features Preview -->
|
||||
<!-- Package Highlights -->
|
||||
<MudStack Spacing="1" Class="mt-2">
|
||||
<MudStack Row="true" Spacing="1" AlignItems="AlignItems.Center">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Check" Size="Size.Small" Color="Color.Primary" />
|
||||
<MudText Typo="Typo.caption">عضویت در باشگاه مشتریان</MudText>
|
||||
</MudStack>
|
||||
<MudStack Row="true" Spacing="1" AlignItems="AlignItems.Center">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Check" Size="Size.Small" Color="Color.Primary" />
|
||||
<MudText Typo="Typo.caption">دریافت پاداش هفتگی</MudText>
|
||||
</MudStack>
|
||||
<MudStack Row="true" Spacing="1" AlignItems="AlignItems.Center">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Check" Size="Size.Small" Color="Color.Primary" />
|
||||
<MudText Typo="Typo.caption">تیمسازی نامحدود</MudText>
|
||||
</MudStack>
|
||||
@if (package.SupportsDirectPurchase)
|
||||
{
|
||||
<MudStack Row="true" Spacing="1" AlignItems="AlignItems.Center">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Check" Size="Size.Small" Color="Color.Primary" />
|
||||
<MudText Typo="Typo.caption">پرداخت مستقیم</MudText>
|
||||
</MudStack>
|
||||
}
|
||||
@if (package.SupportsDayaPurchase)
|
||||
{
|
||||
<MudStack Row="true" Spacing="1" AlignItems="AlignItems.Center">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Check" Size="Size.Small" Color="Color.Primary" />
|
||||
<MudText Typo="Typo.caption">پرداخت با اعتبار دایا</MudText>
|
||||
</MudStack>
|
||||
}
|
||||
@if (package.DiscountMultiplier > 0)
|
||||
{
|
||||
<MudStack Row="true" Spacing="1" AlignItems="AlignItems.Center">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Check" Size="Size.Small" Color="Color.Primary" />
|
||||
<MudText Typo="Typo.caption">ضریب تخفیف: @package.DiscountMultiplier.ToString("F1")x</MudText>
|
||||
</MudStack>
|
||||
}
|
||||
@if (package.MagicWalletMultiplier > 0)
|
||||
{
|
||||
<MudStack Row="true" Spacing="1" AlignItems="AlignItems.Center">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Check" Size="Size.Small" Color="Color.Primary" />
|
||||
<MudText Typo="Typo.caption">کیف پول جادویی: @package.MagicWalletMultiplier.ToString("F1")x</MudText>
|
||||
</MudStack>
|
||||
}
|
||||
@if (package.IsBasePackage)
|
||||
{
|
||||
<MudStack Row="true" Spacing="1" AlignItems="AlignItems.Center">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Star" Size="Size.Small" Color="Color.Warning" />
|
||||
<MudText Typo="Typo.caption">پکیج پایه</MudText>
|
||||
</MudStack>
|
||||
}
|
||||
</MudStack>
|
||||
</MudStack>
|
||||
</MudCardContent>
|
||||
|
||||
@@ -64,36 +64,38 @@ public partial class PackageDetail : IDisposable
|
||||
|
||||
try
|
||||
{
|
||||
// Load package details
|
||||
var packageResponse = await PackageClient.GetPackageAsync(request: new() { Id = Id}, cancellationToken: _loadCts.Token);
|
||||
// Load package details via Customer RPC
|
||||
var response = await PackageClient.GetCustomerPackageDetailsAsync(
|
||||
new GetCustomerPackageDetailsRequest { PackageId = Id },
|
||||
cancellationToken: _loadCts.Token);
|
||||
|
||||
if (packageResponse != null)
|
||||
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 = packageResponse.Id,
|
||||
Title = packageResponse.Title,
|
||||
Body = packageResponse.Description,
|
||||
Image = packageResponse.ImagePath ?? string.Empty,
|
||||
Specifications = new List<Specification>
|
||||
{
|
||||
new() { Name = "ظرفیت", Value = "تا ۲۰۰ عضو", Icon = Icons.Material.Filled.Group },
|
||||
new() { Name = "شجرهنامه", Value = "پیشرفته", Icon = Icons.Material.Filled.AccountTree },
|
||||
new() { Name = "گزارشگیری", Value = "جامع", Icon = Icons.Material.Filled.Analytics },
|
||||
new() { Name = "پشتیبانی", Value = "۲۴ ساعته", Icon = Icons.Material.Filled.Support }
|
||||
},
|
||||
Features = new List<string>
|
||||
{
|
||||
"مدیریت تیم نامحدود",
|
||||
"شجرهنامه بصری",
|
||||
"محاسبه کارمزد خودکار",
|
||||
"گزارشهای مالی",
|
||||
"پشتیبانی اولویتدار"
|
||||
},
|
||||
Id = response.Id,
|
||||
Title = response.Title,
|
||||
Body = response.Description,
|
||||
Image = response.ImagePath ?? string.Empty,
|
||||
Specifications = specs,
|
||||
Features = features,
|
||||
Pricing = new PricingInfo
|
||||
{
|
||||
OriginalPrice = packageResponse.Price,
|
||||
FinalPrice = packageResponse.Price,
|
||||
OriginalPrice = response.Price,
|
||||
FinalPrice = response.Price,
|
||||
HasDiscount = false,
|
||||
DiscountPercent = 0
|
||||
}
|
||||
|
||||
@@ -10,7 +10,15 @@ public record PackageDto(
|
||||
string Title,
|
||||
string Description,
|
||||
string ImageUrl,
|
||||
long Price)
|
||||
long Price,
|
||||
long ActivationFee = 0,
|
||||
double DiscountMultiplier = 0,
|
||||
double MagicWalletMultiplier = 0,
|
||||
long MagicWalletMaxDeposit = 0,
|
||||
long MagicWalletMaxCredit = 0,
|
||||
bool IsBasePackage = false,
|
||||
bool SupportsDayaPurchase = false,
|
||||
bool SupportsDirectPurchase = false)
|
||||
{
|
||||
public string FormattedPrice => string.Format("{0:N0} تومان", Price);
|
||||
}
|
||||
@@ -56,7 +64,15 @@ public class PackageService
|
||||
m.Title,
|
||||
m.Description,
|
||||
m.ImagePath,
|
||||
m.Price))
|
||||
m.Price,
|
||||
m.ActivationFee,
|
||||
m.DiscountMultiplier,
|
||||
m.MagicWalletMultiplier,
|
||||
m.MagicWalletMaxDeposit,
|
||||
m.MagicWalletMaxCredit,
|
||||
m.IsBasePackage,
|
||||
m.SupportsDayaPurchase,
|
||||
m.SupportsDirectPurchase))
|
||||
.ToList();
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -100,16 +116,32 @@ public class PackageService
|
||||
/// <summary>
|
||||
/// Get user's package purchase status
|
||||
/// </summary>
|
||||
public Task<UserPackageStatusDto> GetUserPackageStatusAsync(CancellationToken ct = default)
|
||||
public async Task<UserPackageStatusDto> GetUserPackageStatusAsync(CancellationToken ct = default)
|
||||
{
|
||||
// TODO: Connect to GetUserPackageStatus RPC when available in FrontOffice.BFF
|
||||
return Task.FromResult(new UserPackageStatusDto(
|
||||
HasPurchasedPackage: false,
|
||||
PackageTitle: null,
|
||||
PurchaseMethod: null,
|
||||
IsClubMemberActive: false,
|
||||
WalletBalance: 0,
|
||||
CanActivateClubMembership: false,
|
||||
PurchaseDate: null));
|
||||
try
|
||||
{
|
||||
var response = await _client.GetUserPackageStatusAsync(
|
||||
new GetUserPackageStatusRequest(), cancellationToken: ct);
|
||||
|
||||
return new UserPackageStatusDto(
|
||||
HasPurchasedPackage: response.HasPurchasedPackage,
|
||||
PackageTitle: null,
|
||||
PurchaseMethod: response.PackagePurchaseMethod,
|
||||
IsClubMemberActive: response.IsClubMemberActive,
|
||||
WalletBalance: response.WalletBalance,
|
||||
CanActivateClubMembership: response.CanActivateClubMembership,
|
||||
PurchaseDate: response.LastPurchaseDate?.ToDateTime());
|
||||
}
|
||||
catch
|
||||
{
|
||||
return new UserPackageStatusDto(
|
||||
HasPurchasedPackage: false,
|
||||
PackageTitle: null,
|
||||
PurchaseMethod: null,
|
||||
IsClubMemberActive: false,
|
||||
WalletBalance: 0,
|
||||
CanActivateClubMembership: false,
|
||||
PurchaseDate: null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user