From 30bac23114870c4622afa84f4a656594b917c847 Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Thu, 20 Nov 2025 20:10:17 +0330 Subject: [PATCH] Refactor CartService to use async methods for cart operations and integrate gRPC for backend synchronization --- src/FrontOffice.Main/ConfigureServices.cs | 2 + src/FrontOffice.Main/FrontOffice.Main.csproj | 1 + .../Pages/Store/Cart.razor.cs | 8 +- .../Pages/Store/CheckoutSummary.razor.cs | 2 +- .../Pages/Store/ProductDetail.razor.cs | 4 +- .../Pages/Store/Products.razor.cs | 4 +- src/FrontOffice.Main/Utilities/CartService.cs | 100 +++++++++++++++++- 7 files changed, 107 insertions(+), 14 deletions(-) diff --git a/src/FrontOffice.Main/ConfigureServices.cs b/src/FrontOffice.Main/ConfigureServices.cs index d42c2dd..cec07a8 100644 --- a/src/FrontOffice.Main/ConfigureServices.cs +++ b/src/FrontOffice.Main/ConfigureServices.cs @@ -10,6 +10,7 @@ using FrontOffice.BFF.Transaction.Protobuf.Protos.Transaction; using FrontOffice.BFF.User.Protobuf.Protos.User; using FrontOffice.BFF.UserAddress.Protobuf.Protos.UserAddress; using FrontOffice.BFF.UserOrder.Protobuf.Protos.UserOrder; +using FrontOffice.BFF.ShopingCart.Protobuf.Protos.ShopingCart; using FrontOffice.Main.Utilities; namespace Microsoft.Extensions.DependencyInjection; @@ -78,6 +79,7 @@ public static class ConfigureServices // Products gRPC services.AddScoped(CreateAuthenticatedClient); services.AddScoped(CreateAuthenticatedClient); + services.AddScoped(CreateAuthenticatedClient); return services; } diff --git a/src/FrontOffice.Main/FrontOffice.Main.csproj b/src/FrontOffice.Main/FrontOffice.Main.csproj index 1172bdf..85c3fa6 100644 --- a/src/FrontOffice.Main/FrontOffice.Main.csproj +++ b/src/FrontOffice.Main/FrontOffice.Main.csproj @@ -15,6 +15,7 @@ + diff --git a/src/FrontOffice.Main/Pages/Store/Cart.razor.cs b/src/FrontOffice.Main/Pages/Store/Cart.razor.cs index 3c40f6c..5785560 100644 --- a/src/FrontOffice.Main/Pages/Store/Cart.razor.cs +++ b/src/FrontOffice.Main/Pages/Store/Cart.razor.cs @@ -14,14 +14,14 @@ public partial class Cart : ComponentBase, IDisposable CartService.OnChange += StateHasChanged; } - private void ChangeQty(long productId, int value) + private async Task ChangeQty(long productId, int value) { - CartService.UpdateQuantity(productId, value); + await CartService.UpdateQuantity(productId, value); } - private void Remove(long productId) + private async Task Remove(long productId) { - CartService.Remove(productId); + await CartService.Remove(productId); } private void ProceedCheckout() diff --git a/src/FrontOffice.Main/Pages/Store/CheckoutSummary.razor.cs b/src/FrontOffice.Main/Pages/Store/CheckoutSummary.razor.cs index 84bcff9..0eb41af 100644 --- a/src/FrontOffice.Main/Pages/Store/CheckoutSummary.razor.cs +++ b/src/FrontOffice.Main/Pages/Store/CheckoutSummary.razor.cs @@ -72,7 +72,7 @@ public partial class CheckoutSummary : ComponentBase }; var id = await OrderService.CreateOrderAsync(order); - Cart.Clear(); + await Cart.Clear(); Snackbar.Add("سفارش با موفقیت ثبت شد.", Severity.Success); Navigation.NavigateTo($"{RouteConstants.Store.OrderDetail}{id}"); } diff --git a/src/FrontOffice.Main/Pages/Store/ProductDetail.razor.cs b/src/FrontOffice.Main/Pages/Store/ProductDetail.razor.cs index f498086..4eefe95 100644 --- a/src/FrontOffice.Main/Pages/Store/ProductDetail.razor.cs +++ b/src/FrontOffice.Main/Pages/Store/ProductDetail.razor.cs @@ -21,10 +21,10 @@ public partial class ProductDetail : ComponentBase _loading = false; } - private void AddToCart() + private async Task AddToCart() { if (_product is null) return; - Cart.Add(_product, _qty); + await Cart.Add(_product, _qty); } private static string FormatPrice(long price) => string.Format("{0:N0} تومان", price); diff --git a/src/FrontOffice.Main/Pages/Store/Products.razor.cs b/src/FrontOffice.Main/Pages/Store/Products.razor.cs index b8fad02..1b33f67 100644 --- a/src/FrontOffice.Main/Pages/Store/Products.razor.cs +++ b/src/FrontOffice.Main/Pages/Store/Products.razor.cs @@ -31,9 +31,9 @@ public partial class Products : ComponentBase, IDisposable await Load(); } - private void AddToCart(Product p) + private async Task AddToCart(Product p) { - Cart.Add(p, 1); + await Cart.Add(p, 1); } private static string FormatPrice(long price) => string.Format("{0:N0} تومان", price); diff --git a/src/FrontOffice.Main/Utilities/CartService.cs b/src/FrontOffice.Main/Utilities/CartService.cs index 4902b5c..883e207 100644 --- a/src/FrontOffice.Main/Utilities/CartService.cs +++ b/src/FrontOffice.Main/Utilities/CartService.cs @@ -1,3 +1,6 @@ +using FrontOffice.BFF.ShopingCart.Protobuf.Protos.ShopingCart; +using Google.Protobuf.WellKnownTypes; + namespace FrontOffice.Main.Utilities; public record CartItem(long ProductId, string Title, string ImageUrl, long UnitPrice, int Quantity) @@ -7,14 +10,21 @@ public record CartItem(long ProductId, string Title, string ImageUrl, long UnitP public class CartService { + private readonly ShopingCartContract.ShopingCartContractClient _client; private readonly List _items = new(); public event Action? OnChange; + public CartService(ShopingCartContract.ShopingCartContractClient client) + { + _client = client; + _ = LoadFromServerAsync(); + } + public IReadOnlyList Items => _items.AsReadOnly(); public long Total => _items.Sum(i => i.LineTotal); public int Count => _items.Sum(i => i.Quantity); - public void Add(Product product, int quantity = 1) + public async Task Add(Product product, int quantity = 1) { if (quantity <= 0) return; var existing = _items.FirstOrDefault(i => i.ProductId == product.Id); @@ -28,9 +38,22 @@ public class CartService _items[idx] = existing with { Quantity = existing.Quantity + quantity }; } Notify(); + + try + { + await _client.AddNewUserCartAsync(new AddNewUserCartRequest + { + ProductId = product.Id, + Count = quantity + }); + } + catch + { + // Best-effort sync with backend; keep local state on failure + } } - public void UpdateQuantity(long productId, int quantity) + public async Task UpdateQuantity(long productId, int quantity) { var existing = _items.FirstOrDefault(i => i.ProductId == productId); if (existing is null) return; @@ -42,20 +65,87 @@ public class CartService var idx = _items.IndexOf(existing); _items[idx] = existing with { Quantity = quantity }; Notify(); + + try + { + await _client.UpdateUserCartAsync(new UpdateUserCartRequest + { + UserCartId = productId, + Count = quantity + }); + } + catch + { + // Best-effort sync with backend; keep local state on failure + } } - public void Remove(long productId) + public async Task Remove(long productId) { _items.RemoveAll(i => i.ProductId == productId); Notify(); + + try + { + // Interpret Count=0 as remove from cart + await _client.UpdateUserCartAsync(new UpdateUserCartRequest + { + UserCartId = productId, + Count = 0 + }); + } + catch + { + // Best-effort sync with backend; keep local state on failure + } } - public void Clear() + public async Task Clear() { + var productIds = _items.Select(i => i.ProductId).ToList(); _items.Clear(); Notify(); + + try + { + foreach (var id in productIds) + { + await _client.UpdateUserCartAsync(new UpdateUserCartRequest + { + UserCartId = id, + Count = 0 + }); + } + } + catch + { + // Best-effort sync with backend; keep local state on failure + } } private void Notify() => OnChange?.Invoke(); -} + private async Task LoadFromServerAsync() + { + try + { + var response = await _client.GetAllUserCartAsync(new Empty()); + _items.Clear(); + foreach (var m in response.Models) + { + var item = new CartItem( + ProductId: m.Id, + Title: m.Title ?? string.Empty, + ImageUrl: string.IsNullOrWhiteSpace(m.ImagePath) ? string.Empty : UrlUtility.DownloadUrl + m.ImagePath, + UnitPrice: m.Price, + Quantity: m.SaleCount > 0 ? m.SaleCount : 1); + _items.Add(item); + } + Notify(); + } + catch + { + // If backend is unreachable or user is unauthenticated, fall back to local-only cart. + } + } +}