diff --git a/src/FrontOffice.Main/ConfigureServices.cs b/src/FrontOffice.Main/ConfigureServices.cs index d4f117b..ba78960 100644 --- a/src/FrontOffice.Main/ConfigureServices.cs +++ b/src/FrontOffice.Main/ConfigureServices.cs @@ -51,6 +51,8 @@ public static class ConfigureServices services.AddScoped(); services.AddScoped(); services.AddScoped(); + // Package service + services.AddScoped(); // New services for Club, Network, Commission services.AddScoped(); services.AddScoped(); diff --git a/src/FrontOffice.Main/Pages/Package/MyPackages.razor b/src/FrontOffice.Main/Pages/Package/MyPackages.razor new file mode 100644 index 0000000..6e2b94d --- /dev/null +++ b/src/FrontOffice.Main/Pages/Package/MyPackages.razor @@ -0,0 +1,201 @@ +@attribute [Route(RouteConstants.Package.MyPackages)] + +پکیج‌های من + + + + + + + + پکیج‌های من + + وضعیت پکیج‌های خریداری شده و عضویت باشگاه مشتریان + + + + @if (_isLoading) + { + + + در حال بارگذاری... + + } + else if (_userStatus == null || !_userStatus.HasPurchasedPackage) + { + + + + + شما هنوز پکیجی خریداری نکرده‌اید + + با خرید پکیج طلایی، به باشگاه مشتریان بپیوندید و از مزایای ویژه مانند کمیسیون هفتگی و شبکه‌سازی بهره‌مند شوید. + + + مشاهده پکیج‌ها + + + + } + else + { + + + + + + + + وضعیت پکیج + + + + + + + نوع پکیج: + پکیج طلایی + + + + روش خرید: + + @GetPurchaseMethodText(_userStatus.PurchaseMethod) + + + + @if (_userStatus.PurchaseDate.HasValue) + { + + تاریخ خرید: + @_userStatus.PurchaseDate.Value.ToLocalTime().ToString("yyyy/MM/dd") + + } + + + موجودی کیف پول: + @FormatPrice(_userStatus.WalletBalance) + + + + + + + + + + + + + عضویت باشگاه + + + + + @if (_userStatus.IsClubMemberActive) + { + + عضویت باشگاه مشتریان شما فعال است + + + + + مشاهده شبکه + + + + کمیسیون‌های من + + + } + else if (_userStatus.CanActivateClubMembership) + { + + شما می‌توانید عضویت باشگاه را فعال کنید + + + + فعالسازی باشگاه مشتریان + + } + else + { + + موجودی کیف پول شما برای فعالسازی کافی نیست + + + + برای فعالسازی باشگاه مشتریان، موجودی کیف پول شما باید حداقل ۵۶ میلیون تومان باشد. + + } + + + + + + + + + + + مزایای پکیج طلایی + + + + + + + + + شبکه‌سازی نامحدود + دعوت دوستان و ساخت تیم + + + + + + + کمیسیون هفتگی + دریافت سهم از فروش شبکه + + + + + + + فروشگاه تخفیفی + خرید با تخفیف ویژه + + + + + + + پشتیبانی ۲۴ ساعته + پشتیبانی اولویت‌دار + + + + + + + + } + diff --git a/src/FrontOffice.Main/Pages/Package/MyPackages.razor.cs b/src/FrontOffice.Main/Pages/Package/MyPackages.razor.cs new file mode 100644 index 0000000..21ec012 --- /dev/null +++ b/src/FrontOffice.Main/Pages/Package/MyPackages.razor.cs @@ -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 _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); + } +} diff --git a/src/FrontOffice.Main/Pages/Package/Packages.razor b/src/FrontOffice.Main/Pages/Package/Packages.razor new file mode 100644 index 0000000..18b446d --- /dev/null +++ b/src/FrontOffice.Main/Pages/Package/Packages.razor @@ -0,0 +1,139 @@ +@attribute [Route(RouteConstants.Package.List)] + +پکیج‌ها + + + + + پکیج‌های سرمایه‌گذاری + + با خرید پکیج طلایی، به باشگاه مشتریان بپیوندید و از مزایای ویژه بهره‌مند شوید. + + + + @if (_isLoading) + { + + + در حال بارگذاری پکیج‌ها... + + } + else if (!_packages.Any()) + { + + + هیچ پکیجی یافت نشد + در حال حاضر پکیجی برای نمایش وجود ندارد. + + } + else + { + + @if (_userStatus != null && _userStatus.HasPurchasedPackage) + { + + + شما قبلاً پکیج طلایی را خریداری کرده‌اید. + @if (_userStatus.IsClubMemberActive) + { + عضویت باشگاه شما فعال است. + } + else + { + فعالسازی باشگاه + } + + + } + + + + @foreach (var package in _packages) + { + + + + + + + + @package.Title + + @TruncateDescription(package.Description) + + + + + + @package.FormattedPrice + + + + + + + عضویت در باشگاه مشتریان + + + + دریافت کمیسیون هفتگی + + + + شبکه‌سازی نامحدود + + + + + + + + مشاهده جزئیات + + + + + } + + + + + + + + چرا پکیج طلایی؟ + + با خرید پکیج طلایی، علاوه بر دسترسی به محصولات ویژه، می‌توانید در شبکه فروش شرکت کنید + و از کمیسیون‌های هفتگی بهره‌مند شوید. هر چه شبکه شما گسترده‌تر، درآمد شما بیشتر! + + + + + + سوالات متداول + + + + + } + + + diff --git a/src/FrontOffice.Main/Pages/Package/Packages.razor.cs b/src/FrontOffice.Main/Pages/Package/Packages.razor.cs new file mode 100644 index 0000000..16477f3 --- /dev/null +++ b/src/FrontOffice.Main/Pages/Package/Packages.razor.cs @@ -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 _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] + "..."; + } +} diff --git a/src/FrontOffice.Main/Pages/Profile/ChangePassword.razor b/src/FrontOffice.Main/Pages/Profile/ChangePassword.razor new file mode 100644 index 0000000..da5c583 --- /dev/null +++ b/src/FrontOffice.Main/Pages/Profile/ChangePassword.razor @@ -0,0 +1,70 @@ +@attribute [Route(RouteConstants.Profile.ChangePassword)] + +تغییر رمز عبور + + + + + تغییر رمز عبور + بازگشت + + + + + @if (_success) + { + + رمز عبور با موفقیت تغییر کرد + + } + else + { + + + + + + + @if (!string.IsNullOrEmpty(_error)) + { + @_error + } + + + + @if (_saving) + { + + در حال ذخیره... + } + else + { + تغییر رمز عبور + } + + + } + + + + diff --git a/src/FrontOffice.Main/Pages/Profile/ChangePassword.razor.cs b/src/FrontOffice.Main/Pages/Profile/ChangePassword.razor.cs new file mode 100644 index 0000000..f150645 --- /dev/null +++ b/src/FrontOffice.Main/Pages/Profile/ChangePassword.razor.cs @@ -0,0 +1,77 @@ +using Microsoft.AspNetCore.Components; +using MudBlazor; + +namespace FrontOffice.Main.Pages.Profile; + +public partial class ChangePassword : ComponentBase +{ + private string _currentPassword = string.Empty; + private string _newPassword = string.Empty; + private string _confirmPassword = string.Empty; + private string _error = string.Empty; + private bool _saving; + private bool _success; + + private async Task ChangePasswordAsync() + { + _error = string.Empty; + + // Validation + if (string.IsNullOrWhiteSpace(_currentPassword)) + { + _error = "رمز عبور فعلی را وارد کنید"; + return; + } + + if (string.IsNullOrWhiteSpace(_newPassword)) + { + _error = "رمز عبور جدید را وارد کنید"; + return; + } + + if (_newPassword.Length < 8) + { + _error = "رمز عبور جدید باید حداقل 8 کاراکتر باشد"; + return; + } + + if (_newPassword != _confirmPassword) + { + _error = "رمز عبور جدید و تکرار آن یکسان نیستند"; + return; + } + + if (_currentPassword == _newPassword) + { + _error = "رمز عبور جدید باید متفاوت از رمز فعلی باشد"; + return; + } + + _saving = true; + try + { + // TODO: Call UserContract.ChangePasswordAsync when available + // var request = new ChangePasswordRequest + // { + // CurrentPassword = _currentPassword, + // NewPassword = _newPassword + // }; + // await UserContract.ChangePasswordAsync(request); + + // Simulate API call + await Task.Delay(500); + + _success = true; + Snackbar.Add("رمز عبور با موفقیت تغییر کرد", Severity.Success); + } + catch (Exception ex) + { + _error = ex.Message; + Snackbar.Add($"خطا در تغییر رمز عبور: {ex.Message}", Severity.Error); + } + finally + { + _saving = false; + } + } +} diff --git a/src/FrontOffice.Main/Pages/Store/CheckoutSummary.razor b/src/FrontOffice.Main/Pages/Store/CheckoutSummary.razor index fb8aaac..794bebb 100644 --- a/src/FrontOffice.Main/Pages/Store/CheckoutSummary.razor +++ b/src/FrontOffice.Main/Pages/Store/CheckoutSummary.razor @@ -101,10 +101,24 @@ } - - مبلغ قابل پرداخت - @FormatPrice(Cart.Total) + + @* نمایش جزئیات مالی با مالیات *@ + + + جمع کالاها: + @FormatPrice(Cart.Total) + + + مالیات بر ارزش افزوده (۹%): + @FormatPrice(CalculateVAT()) + + + + مبلغ قابل پرداخت: + @FormatPrice(Cart.Total + CalculateVAT()) + + ثبت سفارش } diff --git a/src/FrontOffice.Main/Pages/Store/CheckoutSummary.razor.cs b/src/FrontOffice.Main/Pages/Store/CheckoutSummary.razor.cs index 3096d99..4de3f0b 100644 --- a/src/FrontOffice.Main/Pages/Store/CheckoutSummary.razor.cs +++ b/src/FrontOffice.Main/Pages/Store/CheckoutSummary.razor.cs @@ -91,6 +91,11 @@ public partial class CheckoutSummary : ComponentBase private static string FormatPrice(long price) => string.Format("{0:N0} تومان", price); + /// + /// محاسبه مالیات بر ارزش افزوده (۹%) + /// + private long CalculateVAT() => (long)(Cart.Total * 0.09); + private static string GetProductImageUrl(string? imageUrl) => string.IsNullOrWhiteSpace(imageUrl) ? "/images/product-placeholder.svg" : imageUrl; } \ No newline at end of file diff --git a/src/FrontOffice.Main/Pages/Store/OrderDetail.razor b/src/FrontOffice.Main/Pages/Store/OrderDetail.razor index 337ee94..742a2c9 100644 --- a/src/FrontOffice.Main/Pages/Store/OrderDetail.razor +++ b/src/FrontOffice.Main/Pages/Store/OrderDetail.razor @@ -74,7 +74,29 @@ else - مبلغ کل: @FormatPrice(_order.FactorDetails.Sum(s=>s.UnitPrice.Value*s.Count.Value)) + + @* نمایش جزئیات مالی *@ + @{ + var subtotal = _order.FactorDetails.Sum(s => s.UnitPrice.Value * s.Count.Value); + var vatAmount = (long)(subtotal * 0.09); + var totalWithVat = subtotal + vatAmount; + } + + + جمع کالاها: + @FormatPrice(subtotal) + + + + مالیات بر ارزش افزوده (۹%): + @FormatPrice(vatAmount) + + + + مبلغ قابل پرداخت: + @FormatPrice(totalWithVat) + + diff --git a/src/FrontOffice.Main/Pages/Store/OrderTracking.razor b/src/FrontOffice.Main/Pages/Store/OrderTracking.razor new file mode 100644 index 0000000..bcaf653 --- /dev/null +++ b/src/FrontOffice.Main/Pages/Store/OrderTracking.razor @@ -0,0 +1,102 @@ +@using FrontOffice.BFF.UserOrder.Protobuf.Protos.UserOrder +@attribute [Route(RouteConstants.Store.OrderTracking + "{id:long}")] + +پیگیری سفارش + + + @if (_loading) + { + + + در حال بارگذاری... + + } + else if (_order is null) + { + سفارش یافت نشد. + + بازگشت به سفارش‌ها + + } + else + { + + + پیگیری سفارش #@_order.Id + + جزئیات سفارش + + + + @* Timeline سفارش *@ + + وضعیت سفارش + + + @foreach (var step in _trackingSteps) + { + + + + + @step.Title + + @step.Description + @if (!string.IsNullOrEmpty(step.Date)) + { + + + @step.Date + + } + + + + } + + + + @* اطلاعات ارسال *@ + + اطلاعات ارسال + + + + + آدرس تحویل + @(_order.UserAddressText ?? "---") + + + + + روش پرداخت + @GetPaymentMethodText(_order.PaymentMethod) + + + + + + @* پشتیبانی *@ + + + + + سوالی دارید؟ + + با پشتیبانی ما تماس بگیرید: ۰۲۱-۱۲۳۴۵۶۷۸ + + + + + تماس با ما + + + + + } + diff --git a/src/FrontOffice.Main/Pages/Store/OrderTracking.razor.cs b/src/FrontOffice.Main/Pages/Store/OrderTracking.razor.cs new file mode 100644 index 0000000..565ea3b --- /dev/null +++ b/src/FrontOffice.Main/Pages/Store/OrderTracking.razor.cs @@ -0,0 +1,125 @@ +using FrontOffice.BFF.UserOrder.Protobuf.Protos.UserOrder; +using FrontOffice.Main.Utilities; +using Microsoft.AspNetCore.Components; + +namespace FrontOffice.Main.Pages.Store; + +public partial class OrderTracking : ComponentBase +{ + [Inject] private OrderService OrderService { get; set; } = default!; + + [Parameter] public long id { get; set; } + + private GetUserOrderResponse? _order; + private bool _loading = true; + private List _trackingSteps = new(); + + protected override async Task OnInitializedAsync() + { + await LoadOrderAsync(); + } + + private async Task LoadOrderAsync() + { + _loading = true; + StateHasChanged(); + + try + { + var response = await OrderService.GetOrderAsync(id); + if (response is not null) + { + _order = response; + BuildTrackingSteps(); + } + } + finally + { + _loading = false; + StateHasChanged(); + } + } + + private void BuildTrackingSteps() + { + if (_order is null) return; + + // Build tracking steps based on PaymentStatus + var isPaid = _order.PaymentStatus == PaymentStatus.Success; + + _trackingSteps = new List + { + new() + { + Title = "ثبت سفارش", + Description = "سفارش شما با موفقیت ثبت شد", + IsCompleted = true, + Date = FormatDate(_order.PaymentDate) + }, + new() + { + Title = "در انتظار پرداخت", + Description = "منتظر تایید پرداخت هستیم", + IsCompleted = isPaid, + IsCurrent = !isPaid, + Date = isPaid ? FormatDate(_order.PaymentDate) : "" + }, + new() + { + Title = "تایید پرداخت", + Description = "پرداخت شما تایید شد", + IsCompleted = isPaid, + IsCurrent = false, + Date = isPaid ? FormatDate(_order.PaymentDate) : "" + }, + new() + { + Title = "در حال پردازش", + Description = "سفارش شما در حال آماده‌سازی است", + IsCompleted = false, + IsCurrent = isPaid, + Date = "" + }, + new() + { + Title = "ارسال شده", + Description = "سفارش شما به پست تحویل داده شد", + IsCompleted = false, + IsCurrent = false, + Date = "" + }, + new() + { + Title = "تحویل داده شده", + Description = "سفارش به دست شما رسید", + IsCompleted = false, + IsCurrent = false, + Date = "" + } + }; + } + + private static string FormatDate(Google.Protobuf.WellKnownTypes.Timestamp? timestamp) + { + if (timestamp is null) return ""; + var date = timestamp.ToDateTime(); + var persianCalendar = new System.Globalization.PersianCalendar(); + return $"{persianCalendar.GetYear(date)}/{persianCalendar.GetMonth(date):00}/{persianCalendar.GetDayOfMonth(date):00}"; + } + + private static string GetPaymentMethodText(PaymentMethod method) => method switch + { + PaymentMethod.Ipg => "پرداخت آنلاین", + PaymentMethod.Wallet => "کیف پول", + _ => "نامشخص" + }; + + private class TrackingStep + { + public string Title { get; set; } = ""; + public string Description { get; set; } = ""; + public bool IsCompleted { get; set; } + public bool IsCurrent { get; set; } + public string Date { get; set; } = ""; + } +} diff --git a/src/FrontOffice.Main/Utilities/PackageService.cs b/src/FrontOffice.Main/Utilities/PackageService.cs new file mode 100644 index 0000000..d83d735 --- /dev/null +++ b/src/FrontOffice.Main/Utilities/PackageService.cs @@ -0,0 +1,121 @@ +using FrontOffice.BFF.Package.Protobuf.Protos.Package; + +namespace FrontOffice.Main.Utilities; + +/// +/// Package data transfer object for UI display +/// +public record PackageDto( + long Id, + string Title, + string Description, + string ImageUrl, + long Price) +{ + public string FormattedPrice => string.Format("{0:N0} تومان", Price); +} + +/// +/// User's package purchase status +/// +public record UserPackageStatusDto( + bool HasPurchasedPackage, + string? PurchaseMethod, // DayaLoan, DirectPurchase, or null + bool IsClubMemberActive, + long WalletBalance, + bool CanActivateClubMembership, + DateTime? PurchaseDate); + +/// +/// Service for managing packages in FrontOffice +/// +public class PackageService +{ + private readonly PackageContract.PackageContractClient _client; + + public PackageService(PackageContract.PackageContractClient client) + { + _client = client; + } + + /// + /// Get all available packages + /// + public async Task> GetAllPackagesAsync(CancellationToken ct = default) + { + try + { + var request = new GetAllPackageByFilterRequest + { + PaginationState = new PaginationState + { + PageNumber = 1, + PageSize = 100 + } + }; + + var response = await _client.GetAllPackageByFilterAsync(request, cancellationToken: ct); + + return response.Models + .Select(m => new PackageDto( + m.Id, + m.Title, + m.Description, + UrlUtility.DownloadUrl + m.ImagePath, + m.Price)) + .ToList(); + } + catch (Exception ex) + { +#if DEBUG + Console.WriteLine($"Error fetching packages: {ex.Message}"); +#endif + return new List(); + } + } + + /// + /// Get a single package by ID + /// + public async Task GetPackageByIdAsync(long id, CancellationToken ct = default) + { + try + { + var response = await _client.GetPackageAsync( + new GetPackageRequest { Id = id }, + cancellationToken: ct); + + if (response == null) return null; + + return new PackageDto( + response.Id, + response.Title, + response.Description, + UrlUtility.DownloadUrl + response.ImagePath, + response.Price); + } + catch (Exception ex) + { +#if DEBUG + Console.WriteLine($"Error fetching package {id}: {ex.Message}"); +#endif + return null; + } + } + + /// + /// Get user's package purchase status (Mock for now - needs BFF implementation) + /// + public Task GetUserPackageStatusAsync(CancellationToken ct = default) + { + // TODO: Connect to GetUserPackageStatus RPC when available in FrontOffice.BFF + // For now, return a mock status + return Task.FromResult(new UserPackageStatusDto( + HasPurchasedPackage: false, + PurchaseMethod: null, + IsClubMemberActive: false, + WalletBalance: 0, + CanActivateClubMembership: false, + PurchaseDate: null)); + } +} diff --git a/src/FrontOffice.Main/Utilities/RouteConstants.cs b/src/FrontOffice.Main/Utilities/RouteConstants.cs index c3a3114..047ec21 100644 --- a/src/FrontOffice.Main/Utilities/RouteConstants.cs +++ b/src/FrontOffice.Main/Utilities/RouteConstants.cs @@ -18,6 +18,7 @@ public static class RouteConstants public const string Personal = "/profile/personal"; public const string Addresses = "/profile/addresses"; public const string Settings = "/profile/settings"; + public const string ChangePassword = "/profile/change-password"; public const string Tree = "/profile/tree"; public const string Wallet = "/profile/wallet"; } @@ -43,7 +44,10 @@ public static class RouteConstants public static class Package { + public const string List = "/packages"; public const string Detail = "/package/"; + public const string MyPackages = "/my-packages"; + public const string Purchase = "/purchase-package/"; } public static class About @@ -74,6 +78,7 @@ public static class RouteConstants public const string CheckoutSummary = "/checkout-summary"; public const string Orders = "/orders"; public const string OrderDetail = "/order/"; // usage: /order/{id} + public const string OrderTracking = "/order-tracking/"; // usage: /order-tracking/{id} public const string Categories = "/categories"; } }