feat: Add package management features and update checkout summary

- Added PackageService to handle package-related operations.
- Introduced MyPackages and Packages pages for user package management.
- Updated CheckoutSummary to display VAT calculations.
- Enhanced OrderDetail to show financial details including VAT.
- Added ChangePassword page for user profile management.
- Implemented OrderTracking page for tracking order status.
- Updated RouteConstants to include new routes for packages and password change.
This commit is contained in:
masoodafar-web
2025-12-05 17:27:04 +03:30
parent feaaeada47
commit 68c114e43e
14 changed files with 1011 additions and 4 deletions
@@ -0,0 +1,201 @@
@attribute [Route(RouteConstants.Package.MyPackages)]
<PageTitle>پکیج‌های من</PageTitle>
<MudContainer MaxWidth="MaxWidth.Large" Class="py-8">
<!-- Breadcrumb -->
<MudBreadcrumbs Items="_breadcrumbItems" Class="mb-4" />
<!-- Header -->
<MudStack Class="mb-6">
<MudText Typo="Typo.h4">پکیج‌های من</MudText>
<MudText Typo="Typo.body1" Class="mud-text-secondary">
وضعیت پکیج‌های خریداری شده و عضویت باشگاه مشتریان
</MudText>
</MudStack>
@if (_isLoading)
{
<MudStack AlignItems="AlignItems.Center" Class="py-16">
<MudProgressCircular Color="Color.Primary" Indeterminate="true" Size="Size.Large" />
<MudText Typo="Typo.body1" Class="mud-text-secondary mt-2">در حال بارگذاری...</MudText>
</MudStack>
}
else if (_userStatus == null || !_userStatus.HasPurchasedPackage)
{
<!-- No Package Purchased -->
<MudPaper Elevation="2" Class="pa-8 text-center">
<MudStack AlignItems="AlignItems.Center" Spacing="4">
<MudIcon Icon="@Icons.Material.Filled.CardGiftcard" Size="Size.Large" Color="Color.Primary" Style="font-size: 80px;" />
<MudText Typo="Typo.h5">شما هنوز پکیجی خریداری نکرده‌اید</MudText>
<MudText Typo="Typo.body1" Class="mud-text-secondary" Style="max-width: 500px;">
با خرید پکیج طلایی، به باشگاه مشتریان بپیوندید و از مزایای ویژه مانند کمیسیون هفتگی و شبکه‌سازی بهره‌مند شوید.
</MudText>
<MudButton Variant="Variant.Filled"
Color="Color.Primary"
Size="Size.Large"
StartIcon="@Icons.Material.Filled.ShoppingCart"
OnClick="@(() => Navigation.NavigateTo(RouteConstants.Package.List))">
مشاهده پکیج‌ها
</MudButton>
</MudStack>
</MudPaper>
}
else
{
<MudGrid Spacing="4">
<!-- Package Status Card -->
<MudItem xs="12" md="6">
<MudPaper Elevation="2" Class="pa-6 h-100">
<MudStack Spacing="3">
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
<MudIcon Icon="@Icons.Material.Filled.Inventory2" Color="Color.Primary" />
<MudText Typo="Typo.h6">وضعیت پکیج</MudText>
</MudStack>
<MudDivider />
<MudStack Spacing="2">
<MudStack Row="true" Justify="Justify.SpaceBetween">
<MudText Typo="Typo.body2" Class="mud-text-secondary">نوع پکیج:</MudText>
<MudText Typo="Typo.body2">پکیج طلایی</MudText>
</MudStack>
<MudStack Row="true" Justify="Justify.SpaceBetween">
<MudText Typo="Typo.body2" Class="mud-text-secondary">روش خرید:</MudText>
<MudChip T="string" Size="Size.Small" Color="@GetPurchaseMethodColor(_userStatus.PurchaseMethod)">
@GetPurchaseMethodText(_userStatus.PurchaseMethod)
</MudChip>
</MudStack>
@if (_userStatus.PurchaseDate.HasValue)
{
<MudStack Row="true" Justify="Justify.SpaceBetween">
<MudText Typo="Typo.body2" Class="mud-text-secondary">تاریخ خرید:</MudText>
<MudText Typo="Typo.body2">@_userStatus.PurchaseDate.Value.ToLocalTime().ToString("yyyy/MM/dd")</MudText>
</MudStack>
}
<MudStack Row="true" Justify="Justify.SpaceBetween">
<MudText Typo="Typo.body2" Class="mud-text-secondary">موجودی کیف پول:</MudText>
<MudText Typo="Typo.body2" Color="Color.Success">@FormatPrice(_userStatus.WalletBalance)</MudText>
</MudStack>
</MudStack>
</MudStack>
</MudPaper>
</MudItem>
<!-- Club Membership Card -->
<MudItem xs="12" md="6">
<MudPaper Elevation="2" Class="pa-6 h-100">
<MudStack Spacing="3">
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
<MudIcon Icon="@Icons.Material.Filled.CardMembership" Color="Color.Secondary" />
<MudText Typo="Typo.h6">عضویت باشگاه</MudText>
</MudStack>
<MudDivider />
@if (_userStatus.IsClubMemberActive)
{
<MudAlert Severity="Severity.Success" Dense="true" Icon="@Icons.Material.Filled.CheckCircle">
عضویت باشگاه مشتریان شما فعال است
</MudAlert>
<MudStack Spacing="2">
<MudButton Variant="Variant.Outlined"
Color="Color.Primary"
FullWidth="true"
StartIcon="@Icons.Material.Filled.AccountTree"
OnClick="@(() => Navigation.NavigateTo(RouteConstants.Network.Statistics))">
مشاهده شبکه
</MudButton>
<MudButton Variant="Variant.Outlined"
Color="Color.Success"
FullWidth="true"
StartIcon="@Icons.Material.Filled.Payments"
OnClick="@(() => Navigation.NavigateTo(RouteConstants.Commission.Dashboard))">
کمیسیون‌های من
</MudButton>
</MudStack>
}
else if (_userStatus.CanActivateClubMembership)
{
<MudAlert Severity="Severity.Info" Dense="true" Icon="@Icons.Material.Filled.Info">
شما می‌توانید عضویت باشگاه را فعال کنید
</MudAlert>
<MudButton Variant="Variant.Filled"
Color="Color.Primary"
FullWidth="true"
Size="Size.Large"
StartIcon="@Icons.Material.Filled.CardMembership"
OnClick="@(() => Navigation.NavigateTo(RouteConstants.Club.Membership))">
فعالسازی باشگاه مشتریان
</MudButton>
}
else
{
<MudAlert Severity="Severity.Warning" Dense="true" Icon="@Icons.Material.Filled.Warning">
موجودی کیف پول شما برای فعالسازی کافی نیست
</MudAlert>
<MudText Typo="Typo.body2" Class="mud-text-secondary">
برای فعالسازی باشگاه مشتریان، موجودی کیف پول شما باید حداقل ۵۶ میلیون تومان باشد.
</MudText>
}
</MudStack>
</MudPaper>
</MudItem>
<!-- Benefits Card -->
<MudItem xs="12">
<MudPaper Elevation="2" Class="pa-6">
<MudStack Spacing="3">
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
<MudIcon Icon="@Icons.Material.Filled.Stars" Color="Color.Warning" />
<MudText Typo="Typo.h6">مزایای پکیج طلایی</MudText>
</MudStack>
<MudDivider />
<MudGrid Spacing="3">
<MudItem xs="12" sm="6" md="3">
<MudPaper Outlined="true" Class="pa-4 text-center">
<MudIcon Icon="@Icons.Material.Filled.Group" Color="Color.Primary" Size="Size.Large" />
<MudText Typo="Typo.subtitle2" Class="mt-2">شبکه‌سازی نامحدود</MudText>
<MudText Typo="Typo.caption" Class="mud-text-secondary">دعوت دوستان و ساخت تیم</MudText>
</MudPaper>
</MudItem>
<MudItem xs="12" sm="6" md="3">
<MudPaper Outlined="true" Class="pa-4 text-center">
<MudIcon Icon="@Icons.Material.Filled.Payments" Color="Color.Success" Size="Size.Large" />
<MudText Typo="Typo.subtitle2" Class="mt-2">کمیسیون هفتگی</MudText>
<MudText Typo="Typo.caption" Class="mud-text-secondary">دریافت سهم از فروش شبکه</MudText>
</MudPaper>
</MudItem>
<MudItem xs="12" sm="6" md="3">
<MudPaper Outlined="true" Class="pa-4 text-center">
<MudIcon Icon="@Icons.Material.Filled.ShoppingBag" Color="Color.Secondary" Size="Size.Large" />
<MudText Typo="Typo.subtitle2" Class="mt-2">فروشگاه تخفیفی</MudText>
<MudText Typo="Typo.caption" Class="mud-text-secondary">خرید با تخفیف ویژه</MudText>
</MudPaper>
</MudItem>
<MudItem xs="12" sm="6" md="3">
<MudPaper Outlined="true" Class="pa-4 text-center">
<MudIcon Icon="@Icons.Material.Filled.Support" Color="Color.Info" Size="Size.Large" />
<MudText Typo="Typo.subtitle2" Class="mt-2">پشتیبانی ۲۴ ساعته</MudText>
<MudText Typo="Typo.caption" Class="mud-text-secondary">پشتیبانی اولویت‌دار</MudText>
</MudPaper>
</MudItem>
</MudGrid>
</MudStack>
</MudPaper>
</MudItem>
</MudGrid>
}
</MudContainer>
@@ -0,0 +1,70 @@
using FrontOffice.Main.Utilities;
using Microsoft.AspNetCore.Components;
using MudBlazor;
namespace FrontOffice.Main.Pages.Package;
public partial class MyPackages : ComponentBase
{
[Inject] private PackageService PackageService { get; set; } = default!;
private UserPackageStatusDto? _userStatus;
private bool _isLoading = true;
private List<BreadcrumbItem> _breadcrumbItems = new()
{
new BreadcrumbItem("صفحه اصلی", RouteConstants.Main.MainPage),
new BreadcrumbItem("پروفایل", RouteConstants.Profile.Index),
new BreadcrumbItem("پکیج‌های من", null, disabled: true)
};
protected override async Task OnInitializedAsync()
{
await LoadDataAsync();
}
private async Task LoadDataAsync()
{
_isLoading = true;
try
{
_userStatus = await PackageService.GetUserPackageStatusAsync();
}
catch (Exception ex)
{
#if DEBUG
Console.WriteLine($"Error loading user package status: {ex.Message}");
#endif
}
finally
{
_isLoading = false;
}
}
private static string GetPurchaseMethodText(string? method)
{
return method switch
{
"DayaLoan" => "وام دایا",
"DirectPurchase" => "پرداخت مستقیم",
_ => "نامشخص"
};
}
private static Color GetPurchaseMethodColor(string? method)
{
return method switch
{
"DayaLoan" => Color.Info,
"DirectPurchase" => Color.Success,
_ => Color.Default
};
}
private static string FormatPrice(long price)
{
return string.Format("{0:N0} تومان", price);
}
}
@@ -0,0 +1,139 @@
@attribute [Route(RouteConstants.Package.List)]
<PageTitle>پکیج‌ها</PageTitle>
<MudContainer MaxWidth="MaxWidth.Large" Class="py-8">
<!-- Header -->
<MudStack Class="mb-6">
<MudText Typo="Typo.h4">پکیج‌های سرمایه‌گذاری</MudText>
<MudText Typo="Typo.body1" Class="mud-text-secondary">
با خرید پکیج طلایی، به باشگاه مشتریان بپیوندید و از مزایای ویژه بهره‌مند شوید.
</MudText>
</MudStack>
@if (_isLoading)
{
<MudStack AlignItems="AlignItems.Center" Class="py-16">
<MudProgressCircular Color="Color.Primary" Indeterminate="true" Size="Size.Large" />
<MudText Typo="Typo.body1" Class="mud-text-secondary mt-2">در حال بارگذاری پکیج‌ها...</MudText>
</MudStack>
}
else if (!_packages.Any())
{
<MudStack AlignItems="AlignItems.Center" Class="py-16">
<MudIcon Icon="@Icons.Material.Filled.Inventory2" Size="Size.Large" Color="Color.Default" />
<MudText Typo="Typo.h6" Class="mt-2">هیچ پکیجی یافت نشد</MudText>
<MudText Typo="Typo.body2" Class="mud-text-secondary">در حال حاضر پکیجی برای نمایش وجود ندارد.</MudText>
</MudStack>
}
else
{
<!-- User Status Card (if logged in) -->
@if (_userStatus != null && _userStatus.HasPurchasedPackage)
{
<MudAlert Severity="Severity.Success" Class="mb-6" Icon="@Icons.Material.Filled.CheckCircle">
<MudText>
شما قبلاً پکیج طلایی را خریداری کرده‌اید.
@if (_userStatus.IsClubMemberActive)
{
<span>عضویت باشگاه شما فعال است.</span>
}
else
{
<MudLink Href="@RouteConstants.Club.Membership" Class="mx-2">فعالسازی باشگاه</MudLink>
}
</MudText>
</MudAlert>
}
<!-- Packages Grid -->
<MudGrid Spacing="4">
@foreach (var package in _packages)
{
<MudItem xs="12" sm="6" md="4">
<MudCard Class="h-100 package-card" Elevation="3">
<!-- Package Image -->
<MudCardMedia Image="@package.ImageUrl"
Title="@package.Title"
Height="200" />
<MudCardContent>
<MudStack Spacing="2">
<MudText Typo="Typo.h5">@package.Title</MudText>
<MudText Typo="Typo.body2" Class="mud-text-secondary" Style="min-height: 60px;">
@TruncateDescription(package.Description)
</MudText>
<!-- Price -->
<MudStack Row="true" AlignItems="AlignItems.Center" Class="mt-2">
<MudIcon Icon="@Icons.Material.Filled.Sell" Color="Color.Success" Size="Size.Small" />
<MudText Typo="Typo.h6" Color="Color.Success">@package.FormattedPrice</MudText>
</MudStack>
<!-- Features Preview -->
<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>
</MudStack>
</MudStack>
</MudCardContent>
<MudCardActions Class="pa-4">
<MudButton Variant="Variant.Outlined"
Color="Color.Primary"
FullWidth="true"
OnClick="@(() => ViewPackageDetails(package.Id))">
مشاهده جزئیات
</MudButton>
</MudCardActions>
</MudCard>
</MudItem>
}
</MudGrid>
<!-- Info Section -->
<MudPaper Elevation="0" Class="pa-6 mt-8 rounded-xl" Style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);">
<MudGrid Spacing="4">
<MudItem xs="12" md="8">
<MudStack Spacing="2">
<MudText Typo="Typo.h5" Style="color: white;">چرا پکیج طلایی؟</MudText>
<MudText Typo="Typo.body1" Style="color: rgba(255,255,255,0.9);">
با خرید پکیج طلایی، علاوه بر دسترسی به محصولات ویژه، می‌توانید در شبکه فروش شرکت کنید
و از کمیسیون‌های هفتگی بهره‌مند شوید. هر چه شبکه شما گسترده‌تر، درآمد شما بیشتر!
</MudText>
</MudStack>
</MudItem>
<MudItem xs="12" md="4" Class="d-flex align-center justify-center">
<MudButton Variant="Variant.Filled"
Color="Color.Default"
Size="Size.Large"
StartIcon="@Icons.Material.Filled.HelpOutline"
OnClick="@(() => Navigation.NavigateTo(RouteConstants.FAQ.Index))">
سوالات متداول
</MudButton>
</MudItem>
</MudGrid>
</MudPaper>
}
</MudContainer>
<style>
.package-card {
transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
}
.package-card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15) !important;
}
</style>
@@ -0,0 +1,54 @@
using FrontOffice.Main.Utilities;
using Microsoft.AspNetCore.Components;
namespace FrontOffice.Main.Pages.Package;
public partial class Packages : ComponentBase
{
[Inject] private PackageService PackageService { get; set; } = default!;
private List<PackageDto> _packages = new();
private UserPackageStatusDto? _userStatus;
private bool _isLoading = true;
protected override async Task OnInitializedAsync()
{
await LoadDataAsync();
}
private async Task LoadDataAsync()
{
_isLoading = true;
try
{
// Load packages
_packages = await PackageService.GetAllPackagesAsync();
// Load user status (if authenticated)
_userStatus = await PackageService.GetUserPackageStatusAsync();
}
catch (Exception ex)
{
#if DEBUG
Console.WriteLine($"Error loading packages: {ex.Message}");
#endif
}
finally
{
_isLoading = false;
}
}
private void ViewPackageDetails(long packageId)
{
Navigation.NavigateTo(RouteConstants.Package.Detail + packageId);
}
private static string TruncateDescription(string description, int maxLength = 100)
{
if (string.IsNullOrEmpty(description)) return string.Empty;
if (description.Length <= maxLength) return description;
return description[..maxLength] + "...";
}
}