Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7a11572294 | |||
| 3bce80c963 | |||
| d3e9b30be5 | |||
| a75bf5951d | |||
| a3bc2bc3f9 |
@@ -158,7 +158,7 @@
|
|||||||
</svg>
|
</svg>
|
||||||
@if (CartItems.Any())
|
@if (CartItems.Any())
|
||||||
{
|
{
|
||||||
<span class="cart-badge">@CartItems.Sum(i => i.Quantity)</span>
|
<span class="cart-badge">@ShoppingCart.Items.Count</span>
|
||||||
}
|
}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,19 @@
|
|||||||
using Microsoft.AspNetCore.Components.Routing;
|
using MidrandBookshop.Services.ShoppingCart;
|
||||||
using Microsoft.AspNetCore.Components.Web;
|
using MidrandBookshop.Services.ShoppingCart.Models;
|
||||||
using Microsoft.AspNetCore.WebUtilities;
|
|
||||||
|
|
||||||
namespace MidrandBookshop.Components.Layout;
|
namespace MidrandBookshop.Components.Layout;
|
||||||
|
|
||||||
public partial class MainLayout : IDisposable
|
public partial class MainLayout(CartService cartService) : IDisposable
|
||||||
{
|
{
|
||||||
|
[Inject]
|
||||||
|
private AuthenticationStateProvider AuthStateProvider { get; set; } = default!;
|
||||||
|
|
||||||
|
private Cart ShoppingCart => cartService.ShoppingCart;
|
||||||
|
|
||||||
|
private AuthenticationState? AuthState { get; set; }
|
||||||
|
private System.Security.Claims.ClaimsPrincipal? User { get; set; }
|
||||||
|
private bool IsAuthenticated => User?.Identity?.IsAuthenticated ?? false;
|
||||||
|
|
||||||
private string SearchInputBuffer { get; set; } = string.Empty;
|
private string SearchInputBuffer { get; set; } = string.Empty;
|
||||||
private string GlobalSearchQuery { get; set; } = string.Empty;
|
private string GlobalSearchQuery { get; set; } = string.Empty;
|
||||||
private bool IsSearchActive { get; set; } = false;
|
private bool IsSearchActive { get; set; } = false;
|
||||||
@@ -18,19 +26,25 @@ public partial class MainLayout : IDisposable
|
|||||||
new CartItem { Id = 3, Title = "Album Architectures, Maputo", Author = "Guedes Archive", Price = 350, Quantity = 1 }
|
new CartItem { Id = 3, Title = "Album Architectures, Maputo", Author = "Guedes Archive", Price = 350, Quantity = 1 }
|
||||||
};
|
};
|
||||||
|
|
||||||
private void TriggerHeaderLogout()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
// Force tear-down of the active client websocket pipeline safely
|
AuthState = await AuthStateProvider.GetAuthenticationStateAsync();
|
||||||
Navigation.NavigateTo("/logout", forceLoad: true);
|
User = AuthState!.User;
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnInitialized()
|
|
||||||
{
|
|
||||||
Navigation.LocationChanged += OnLocationChanged;
|
Navigation.LocationChanged += OnLocationChanged;
|
||||||
|
cartService.OnCartChanged += CartService_OnCartChanged;
|
||||||
|
|
||||||
SyncSearchQueryFromUrl();
|
SyncSearchQueryFromUrl();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
|
{
|
||||||
|
if (firstRender)
|
||||||
|
await cartService.LoadCartFromStorageAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void CartService_OnCartChanged() => await InvokeAsync(StateHasChanged);
|
||||||
|
|
||||||
private void OnLocationChanged(object? sender, LocationChangedEventArgs e)
|
private void OnLocationChanged(object? sender, LocationChangedEventArgs e)
|
||||||
{
|
{
|
||||||
SyncSearchQueryFromUrl();
|
SyncSearchQueryFromUrl();
|
||||||
@@ -107,7 +121,8 @@ public partial class MainLayout : IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void RemoveFromCart(CartItem item) => CartItems.Remove(item);
|
private void RemoveFromCart(CartItem item) => CartItems.Remove(item);
|
||||||
private int GetCartTotal() => CartItems.Sum(item => item.Price * item.Quantity);
|
|
||||||
|
private decimal GetCartTotal() => ShoppingCart?.TotalPrice ?? 0.00m;
|
||||||
|
|
||||||
private void RedirectToCart()
|
private void RedirectToCart()
|
||||||
{
|
{
|
||||||
@@ -124,6 +139,9 @@ public partial class MainLayout : IDisposable
|
|||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
Navigation.LocationChanged -= OnLocationChanged;
|
Navigation.LocationChanged -= OnLocationChanged;
|
||||||
|
cartService.OnCartChanged -= CartService_OnCartChanged;
|
||||||
|
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class CartItem
|
public class CartItem
|
||||||
|
|||||||
@@ -225,77 +225,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@code {
|
|
||||||
private bool showAddForm = false;
|
|
||||||
private AddressItem? editingAddress = null;
|
|
||||||
private string newAddressName = "";
|
|
||||||
private string newStreetAddress = "";
|
|
||||||
private string newCity = "";
|
|
||||||
private string newPostalCode = "";
|
|
||||||
private bool isBilling, isShipping;
|
|
||||||
|
|
||||||
private List<OrderItem> orderHistory = new()
|
|
||||||
{
|
|
||||||
new OrderItem { OrderId = "#MB-2026-9481", ProductId = "introduction-to-blazor", ProductTitle = "Introduction to Blazor WebAssembly Framework Development", OrderDate = new DateTime(2026, 5, 20), ShippingAddressName = "Home Address", Status = "Shipped", Total = 720.00 },
|
|
||||||
new OrderItem { OrderId = "#MB-2026-8712", ProductId = "mastering-css-isolation", ProductTitle = "Mastering CSS Isolation in Modern .NET Web Applications Architecture", OrderDate = new DateTime(2026, 4, 14), ShippingAddressName = "Midrand Books Warehouse", Status = "Delivered", Total = 890.00 }
|
|
||||||
};
|
|
||||||
|
|
||||||
private List<AddressItem> savedAddresses = new()
|
|
||||||
{
|
|
||||||
new AddressItem { Id = 1, Name = "Home Address", Street = "12 Main Road", City = "Midrand", PostalCode = "1685", IsBilling = true, IsShipping = true, IsPrimary = true },
|
|
||||||
new AddressItem { Id = 2, Name = "Corporate Office", Street = "45 Challink Street", City = "Halfway House", PostalCode = "1682", IsBilling = true, IsShipping = false, IsPrimary = false },
|
|
||||||
new AddressItem { Id = 3, Name = "Midrand Books Warehouse", Street = "Unit 8, Corporate Park North", City = "Randjespark", PostalCode = "1683", IsBilling = false, IsShipping = true, IsPrimary = false }
|
|
||||||
};
|
|
||||||
|
|
||||||
private void TriggerLogout() => Navigation.NavigateTo("/logout", forceLoad: true);
|
|
||||||
private void DownloadInvoice(string orderId) { /* Handle download sequence here */ }
|
|
||||||
private void OpenAddForm() { editingAddress = null; showAddForm = true; }
|
|
||||||
|
|
||||||
private void SaveAddress()
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrWhiteSpace(newAddressName) && !string.IsNullOrWhiteSpace(newStreetAddress))
|
|
||||||
{
|
|
||||||
var nextId = savedAddresses.Any() ? savedAddresses.Max(a => a.Id) + 1 : 1;
|
|
||||||
savedAddresses.Add(new AddressItem
|
|
||||||
{
|
|
||||||
Id = nextId,
|
|
||||||
Name = newAddressName,
|
|
||||||
Street = newStreetAddress,
|
|
||||||
City = newCity,
|
|
||||||
PostalCode = newPostalCode,
|
|
||||||
IsBilling = isBilling,
|
|
||||||
IsShipping = isShipping,
|
|
||||||
IsPrimary = !savedAddresses.Any()
|
|
||||||
});
|
|
||||||
ResetAddForm();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ResetAddForm() { newAddressName = ""; newStreetAddress = ""; newCity = ""; newPostalCode = ""; isBilling = isShipping = showAddForm = false; }
|
|
||||||
private void StartEditing(AddressItem addr) { showAddForm = false; editingAddress = new AddressItem { Id = addr.Id, Name = addr.Name, Street = addr.Street, City = addr.City, PostalCode = addr.PostalCode, IsBilling = addr.IsBilling, IsShipping = addr.IsShipping, IsPrimary = addr.IsPrimary }; }
|
|
||||||
private void CancelEditing() => editingAddress = null;
|
|
||||||
|
|
||||||
private void UpdateAddress()
|
|
||||||
{
|
|
||||||
if (editingAddress != null)
|
|
||||||
{
|
|
||||||
var target = savedAddresses.FirstOrDefault(a => a.Id == editingAddress.Id);
|
|
||||||
if (target != null) { target.Name = editingAddress.Name; target.Street = editingAddress.Street; target.City = editingAddress.City; target.PostalCode = editingAddress.PostalCode; target.IsBilling = editingAddress.IsBilling; target.IsShipping = editingAddress.IsShipping; }
|
|
||||||
editingAddress = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DeleteAddress(AddressItem addr) { if (editingAddress?.Id == addr.Id) editingAddress = null; savedAddresses.Remove(addr); if (addr.IsPrimary && savedAddresses.Any()) savedAddresses.First().IsPrimary = true; }
|
|
||||||
|
|
||||||
private void SetPrimary(AddressItem target, ChangeEventArgs e)
|
|
||||||
{
|
|
||||||
var isChecked = (bool)(e.Value ?? false);
|
|
||||||
if (isChecked) { foreach (var addr in savedAddresses) addr.IsPrimary = (addr.Id == target.Id); }
|
|
||||||
else target.IsPrimary = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public class AddressItem { public int Id { get; set; } public string Name { get; set; } = ""; public string Street { get; set; } = ""; public string City { get; set; } = ""; public string PostalCode { get; set; } = ""; public bool IsBilling { get; set; } public bool IsShipping { get; set; } public bool IsPrimary { get; set; } }
|
|
||||||
public class OrderItem { public string OrderId { get; set; } = ""; public string ProductId { get; set; } = ""; public string ProductTitle { get; set; } = ""; public DateTime OrderDate { get; set; } public string ShippingAddressName { get; set; } = ""; public string Status { get; set; } = ""; public double Total { get; set; } }
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
namespace MidrandBookshop.Components.Pages;
|
||||||
|
|
||||||
|
public partial class Account
|
||||||
|
{
|
||||||
|
private bool showAddForm = false;
|
||||||
|
private AddressItem? editingAddress = null;
|
||||||
|
private string newAddressName = "";
|
||||||
|
private string newStreetAddress = "";
|
||||||
|
private string newCity = "";
|
||||||
|
private string newPostalCode = "";
|
||||||
|
private bool isBilling, isShipping;
|
||||||
|
|
||||||
|
private List<OrderItem> orderHistory = new()
|
||||||
|
{
|
||||||
|
new OrderItem { OrderId = "#MB-2026-9481", ProductId = "introduction-to-blazor", ProductTitle = "Introduction to Blazor WebAssembly Framework Development", OrderDate = new DateTime(2026, 5, 20), ShippingAddressName = "Home Address", Status = "Shipped", Total = 720.00 },
|
||||||
|
new OrderItem { OrderId = "#MB-2026-8712", ProductId = "mastering-css-isolation", ProductTitle = "Mastering CSS Isolation in Modern .NET Web Applications Architecture", OrderDate = new DateTime(2026, 4, 14), ShippingAddressName = "Midrand Books Warehouse", Status = "Delivered", Total = 890.00 }
|
||||||
|
};
|
||||||
|
|
||||||
|
private List<AddressItem> savedAddresses = new()
|
||||||
|
{
|
||||||
|
new AddressItem { Id = 1, Name = "Home Address", Street = "12 Main Road", City = "Midrand", PostalCode = "1685", IsBilling = true, IsShipping = true, IsPrimary = true },
|
||||||
|
new AddressItem { Id = 2, Name = "Corporate Office", Street = "45 Challink Street", City = "Halfway House", PostalCode = "1682", IsBilling = true, IsShipping = false, IsPrimary = false },
|
||||||
|
new AddressItem { Id = 3, Name = "Midrand Books Warehouse", Street = "Unit 8, Corporate Park North", City = "Randjespark", PostalCode = "1683", IsBilling = false, IsShipping = true, IsPrimary = false }
|
||||||
|
};
|
||||||
|
|
||||||
|
private void TriggerLogout() => Navigation.NavigateTo("/logout", forceLoad: true);
|
||||||
|
private void DownloadInvoice(string orderId) { /* Handle download sequence here */ }
|
||||||
|
private void OpenAddForm() { editingAddress = null; showAddForm = true; }
|
||||||
|
|
||||||
|
private void SaveAddress()
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(newAddressName) && !string.IsNullOrWhiteSpace(newStreetAddress))
|
||||||
|
{
|
||||||
|
var nextId = savedAddresses.Any() ? savedAddresses.Max(a => a.Id) + 1 : 1;
|
||||||
|
savedAddresses.Add(new AddressItem
|
||||||
|
{
|
||||||
|
Id = nextId,
|
||||||
|
Name = newAddressName,
|
||||||
|
Street = newStreetAddress,
|
||||||
|
City = newCity,
|
||||||
|
PostalCode = newPostalCode,
|
||||||
|
IsBilling = isBilling,
|
||||||
|
IsShipping = isShipping,
|
||||||
|
IsPrimary = !savedAddresses.Any()
|
||||||
|
});
|
||||||
|
ResetAddForm();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ResetAddForm() { newAddressName = ""; newStreetAddress = ""; newCity = ""; newPostalCode = ""; isBilling = isShipping = showAddForm = false; }
|
||||||
|
private void StartEditing(AddressItem addr) { showAddForm = false; editingAddress = new AddressItem { Id = addr.Id, Name = addr.Name, Street = addr.Street, City = addr.City, PostalCode = addr.PostalCode, IsBilling = addr.IsBilling, IsShipping = addr.IsShipping, IsPrimary = addr.IsPrimary }; }
|
||||||
|
private void CancelEditing() => editingAddress = null;
|
||||||
|
|
||||||
|
private void UpdateAddress()
|
||||||
|
{
|
||||||
|
if (editingAddress != null)
|
||||||
|
{
|
||||||
|
var target = savedAddresses.FirstOrDefault(a => a.Id == editingAddress.Id);
|
||||||
|
if (target != null) { target.Name = editingAddress.Name; target.Street = editingAddress.Street; target.City = editingAddress.City; target.PostalCode = editingAddress.PostalCode; target.IsBilling = editingAddress.IsBilling; target.IsShipping = editingAddress.IsShipping; }
|
||||||
|
editingAddress = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DeleteAddress(AddressItem addr) { if (editingAddress?.Id == addr.Id) editingAddress = null; savedAddresses.Remove(addr); if (addr.IsPrimary && savedAddresses.Any()) savedAddresses.First().IsPrimary = true; }
|
||||||
|
|
||||||
|
private void SetPrimary(AddressItem target, ChangeEventArgs e)
|
||||||
|
{
|
||||||
|
var isChecked = (bool)(e.Value ?? false);
|
||||||
|
if (isChecked) { foreach (var addr in savedAddresses) addr.IsPrimary = (addr.Id == target.Id); }
|
||||||
|
else target.IsPrimary = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AddressItem { public int Id { get; set; } public string Name { get; set; } = ""; public string Street { get; set; } = ""; public string City { get; set; } = ""; public string PostalCode { get; set; } = ""; public bool IsBilling { get; set; } public bool IsShipping { get; set; } public bool IsPrimary { get; set; } }
|
||||||
|
public class OrderItem { public string OrderId { get; set; } = ""; public string ProductId { get; set; } = ""; public string ProductTitle { get; set; } = ""; public DateTime OrderDate { get; set; } public string ShippingAddressName { get; set; } = ""; public string Status { get; set; } = ""; public double Total { get; set; } }
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ using LiteCharms.Features.MidrandBooks.Authors;
|
|||||||
using LiteCharms.Features.MidrandBooks.Authors.Models;
|
using LiteCharms.Features.MidrandBooks.Authors.Models;
|
||||||
using LiteCharms.Features.MidrandBooks.Products;
|
using LiteCharms.Features.MidrandBooks.Products;
|
||||||
using LiteCharms.Features.MidrandBooks.Products.Models;
|
using LiteCharms.Features.MidrandBooks.Products.Models;
|
||||||
|
using MidrandBookshop.Services.ShoppingCart;
|
||||||
|
|
||||||
namespace MidrandBookshop.Components.Pages;
|
namespace MidrandBookshop.Components.Pages;
|
||||||
|
|
||||||
@@ -12,10 +13,12 @@ public partial class ProductView : ComponentBase
|
|||||||
|
|
||||||
[Inject] private ProductService ProductService { get; set; } = default!;
|
[Inject] private ProductService ProductService { get; set; } = default!;
|
||||||
[Inject] private AuthorService AuthorService { get; set; } = default!;
|
[Inject] private AuthorService AuthorService { get; set; } = default!;
|
||||||
|
[Inject] private CartService CartService { get; set; } = default!;
|
||||||
[Inject] private NavigationManager Navigation { get; set; } = default!;
|
[Inject] private NavigationManager Navigation { get; set; } = default!;
|
||||||
|
|
||||||
protected bool IsLoading { get; private set; } = true;
|
protected bool IsLoading { get; private set; } = true;
|
||||||
protected Product? CurrentProduct { get; private set; }
|
protected Product? CurrentProduct { get; private set; }
|
||||||
|
protected ProductPrice? CurrentPrice { get; private set; }
|
||||||
protected decimal LivePrice { get; private set; } = 0.00m;
|
protected decimal LivePrice { get; private set; } = 0.00m;
|
||||||
protected string AuthorName { get; private set; } = "Unknown Author";
|
protected string AuthorName { get; private set; } = "Unknown Author";
|
||||||
protected string PrimaryCategory { get; private set; } = "General";
|
protected string PrimaryCategory { get; private set; } = "General";
|
||||||
@@ -44,6 +47,7 @@ public partial class ProductView : ComponentBase
|
|||||||
|
|
||||||
var priceResult = await ProductService.GetProductPriceAsync(BookId);
|
var priceResult = await ProductService.GetProductPriceAsync(BookId);
|
||||||
LivePrice = priceResult.IsSuccess ? priceResult.Value.Amount : 0m;
|
LivePrice = priceResult.IsSuccess ? priceResult.Value.Amount : 0m;
|
||||||
|
CurrentPrice = priceResult.IsSuccess ? priceResult.Value : null;
|
||||||
|
|
||||||
var categoryResult = await ProductService.GetProductCategoriesAsync(BookId);
|
var categoryResult = await ProductService.GetProductCategoriesAsync(BookId);
|
||||||
if (categoryResult.IsSuccess && categoryResult.Value.Length > 0)
|
if (categoryResult.IsSuccess && categoryResult.Value.Length > 0)
|
||||||
@@ -73,6 +77,7 @@ public partial class ProductView : ComponentBase
|
|||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
CurrentProduct = null;
|
CurrentProduct = null;
|
||||||
|
CurrentPrice = null;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -80,14 +85,34 @@ public partial class ProductView : ComponentBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void IncreaseQty() => Quantity++;
|
protected void IncreaseQty()
|
||||||
protected void DecreaseQty() { if (Quantity > 1) Quantity--; }
|
{
|
||||||
|
Quantity++;
|
||||||
|
|
||||||
protected void HandleAddToCart()
|
if (CurrentPrice is not null)
|
||||||
|
CartService.UpdateQuantity(CurrentPrice!.Id, Quantity);
|
||||||
|
}
|
||||||
|
protected void DecreaseQty()
|
||||||
|
{
|
||||||
|
if (Quantity > 1)
|
||||||
|
{
|
||||||
|
Quantity--;
|
||||||
|
|
||||||
|
CartService.UpdateQuantity(CurrentPrice!.Id, Quantity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async void HandleAddToCart()
|
||||||
{
|
{
|
||||||
if (CurrentProduct == null) return;
|
if (CurrentProduct == null) return;
|
||||||
|
|
||||||
|
if (CurrentPrice is not null)
|
||||||
|
{
|
||||||
|
CartService.AddItem(CurrentPrice);
|
||||||
|
Quantity++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void ViewAllAuthorBooks()
|
protected void ViewAllAuthorBooks()
|
||||||
{
|
{
|
||||||
if (CurrentAuthor is not null)
|
if (CurrentAuthor is not null)
|
||||||
|
|||||||
@@ -18,13 +18,13 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="LiteCharms.Features" Version="1.98.0" />
|
<PackageReference Include="LiteCharms.Features" Version="1.101.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<!-- UI -->
|
<!-- UI -->
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ANM.Blazored.Toast" Version="0.1.1" />
|
<PackageReference Include="ANM.Blazored.Toast" Version="0.1.1" />
|
||||||
<PackageReference Include="LiteCharms.Features.MidrandBooks" Version="1.98.0" />
|
<PackageReference Include="LiteCharms.Features.MidrandBooks" Version="1.101.0" />
|
||||||
|
|
||||||
<!-- Global Usings -->
|
<!-- Global Usings -->
|
||||||
<Using Include="Blazored.Toast.Services" />
|
<Using Include="Blazored.Toast.Services" />
|
||||||
@@ -51,6 +51,10 @@
|
|||||||
<!-- Shared Global Usings -->
|
<!-- Shared Global Usings -->
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Using Include="Blazored.Toast" />
|
<Using Include="Blazored.Toast" />
|
||||||
|
<Using Include="Microsoft.AspNetCore.Components.Authorization" />
|
||||||
|
<Using Include="Microsoft.AspNetCore.Components.Routing" />
|
||||||
|
<Using Include="Microsoft.AspNetCore.Components.Web" />
|
||||||
|
<Using Include="Microsoft.AspNetCore.WebUtilities" />
|
||||||
<Using Include="Microsoft.AspNetCore.Components" />
|
<Using Include="Microsoft.AspNetCore.Components" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using LiteCharms.Features.Mediator;
|
|||||||
using LiteCharms.Features.MidrandBooks.Extensions;
|
using LiteCharms.Features.MidrandBooks.Extensions;
|
||||||
using Microsoft.AspNetCore.HttpOverrides;
|
using Microsoft.AspNetCore.HttpOverrides;
|
||||||
using MidrandBookshop.Components;
|
using MidrandBookshop.Components;
|
||||||
|
using MidrandBookshop.Services.ShoppingCart;
|
||||||
using static LiteCharms.Features.Extensions.Quartz;
|
using static LiteCharms.Features.Extensions.Quartz;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
@@ -26,6 +27,7 @@ builder.Services.AddEmailServiceBus();
|
|||||||
|
|
||||||
builder.Services.AddHttpClient();
|
builder.Services.AddHttpClient();
|
||||||
builder.Services.AddShopServices();
|
builder.Services.AddShopServices();
|
||||||
|
builder.Services.AddScoped<CartService>();
|
||||||
builder.Services.AddHashServices(builder.Configuration);
|
builder.Services.AddHashServices(builder.Configuration);
|
||||||
builder.Services.AddMidrandShopDatabase(builder.Configuration);
|
builder.Services.AddMidrandShopDatabase(builder.Configuration);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,153 @@
|
|||||||
|
using LiteCharms.Features.Browser;
|
||||||
|
using LiteCharms.Features.Hasher;
|
||||||
|
using LiteCharms.Features.MidrandBooks.Products.Models;
|
||||||
|
using MidrandBookshop.Services.ShoppingCart.Models;
|
||||||
|
|
||||||
|
namespace MidrandBookshop.Services.ShoppingCart;
|
||||||
|
|
||||||
|
public sealed class CartService(LocalStorageService localStorage)
|
||||||
|
{
|
||||||
|
private readonly string CartStorageKey = HashService.ToMd5Hash(nameof(Cart)).Value;
|
||||||
|
|
||||||
|
public Cart ShoppingCart { get; private set; } = new();
|
||||||
|
|
||||||
|
public event Action? OnCartChanged;
|
||||||
|
|
||||||
|
public Cart GetCart() => ShoppingCart;
|
||||||
|
|
||||||
|
public void NotifyStateChanged() => OnCartChanged?.Invoke();
|
||||||
|
|
||||||
|
public async Task LoadCartFromStorageAsync()
|
||||||
|
{
|
||||||
|
var loadResult = await localStorage.GetAsync<Cart>(CartStorageKey);
|
||||||
|
|
||||||
|
if (loadResult.IsFailed) await localStorage.SaveAsync(CartStorageKey, ShoppingCart);
|
||||||
|
|
||||||
|
if (loadResult.IsSuccess) ShoppingCart = loadResult.Value;
|
||||||
|
|
||||||
|
NotifyStateChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SaveCartToStorageAsync() => await localStorage.SaveAsync(CartStorageKey, ShoppingCart);
|
||||||
|
|
||||||
|
public void AddItem(ProductPrice productPrice)
|
||||||
|
{
|
||||||
|
var itemExists = false;
|
||||||
|
|
||||||
|
for (var i = 0; i < ShoppingCart.Items.Count; i++)
|
||||||
|
{
|
||||||
|
if (ShoppingCart.Items[i].Price!.Id == productPrice.Id)
|
||||||
|
{
|
||||||
|
ShoppingCart.Items[i].Quantity++;
|
||||||
|
ShoppingCart.Items[i].Amount += productPrice.Amount;
|
||||||
|
|
||||||
|
itemExists = true;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!itemExists)
|
||||||
|
ShoppingCart.Items.Add(new CartItem
|
||||||
|
{
|
||||||
|
Price = productPrice,
|
||||||
|
Amount = productPrice.Amount,
|
||||||
|
Quantity = 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
CalculateTotalPrice();
|
||||||
|
NotifyStateChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateQuantity(long productPriceId, int newQuantity)
|
||||||
|
{
|
||||||
|
if (newQuantity <= 0)
|
||||||
|
{
|
||||||
|
RemoveAllSameItem(productPriceId);
|
||||||
|
NotifyStateChanged();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < ShoppingCart.Items.Count; i++)
|
||||||
|
{
|
||||||
|
if (ShoppingCart.Items[i].Price!.Id == productPriceId)
|
||||||
|
{
|
||||||
|
var oldQuantity = ShoppingCart.Items[i].Quantity;
|
||||||
|
var pricePerUnit = ShoppingCart.Items[i].Price!.Amount;
|
||||||
|
|
||||||
|
ShoppingCart.Items[i].Quantity = newQuantity;
|
||||||
|
ShoppingCart.Items[i].Amount = pricePerUnit * newQuantity;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CalculateTotalPrice();
|
||||||
|
NotifyStateChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveOneItem(long productPriceId)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < ShoppingCart.Items.Count; i++)
|
||||||
|
{
|
||||||
|
if (ShoppingCart.Items[i].Price!.Id == productPriceId)
|
||||||
|
{
|
||||||
|
if (ShoppingCart.Items[i].Quantity <= 1)
|
||||||
|
{
|
||||||
|
ShoppingCart.Items.RemoveAt(i);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ShoppingCart.Items[i].Quantity--;
|
||||||
|
ShoppingCart.Items[i].Amount -= ShoppingCart.Items[i].Price!.Amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CalculateTotalPrice();
|
||||||
|
NotifyStateChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveAllSameItem(long productPriceId)
|
||||||
|
{
|
||||||
|
if (ShoppingCart.Items.Count == 0) return;
|
||||||
|
|
||||||
|
var item = ShoppingCart.Items.FirstOrDefault(i => i.Price?.Id == productPriceId);
|
||||||
|
|
||||||
|
if (item is not null) ShoppingCart.Items.Remove(item);
|
||||||
|
|
||||||
|
CalculateTotalPrice();
|
||||||
|
NotifyStateChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
if(ShoppingCart.CustomerId is not null || ShoppingCart.OrderId is not null)
|
||||||
|
{
|
||||||
|
ShoppingCart.TotalPrice = 0;
|
||||||
|
ShoppingCart.TotalVat = 0;
|
||||||
|
ShoppingCart.Items.Clear();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ShoppingCart = new Cart();
|
||||||
|
|
||||||
|
NotifyStateChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public decimal CalculateTotalPrice()
|
||||||
|
{
|
||||||
|
if (ShoppingCart.Items.Count == 0) return 0;
|
||||||
|
|
||||||
|
var gross = ShoppingCart.Items.Sum(i => i.Amount);
|
||||||
|
|
||||||
|
if (!ShoppingCart.IsVatInclusive) ShoppingCart.TotalVat = gross * ShoppingCart.VatRate;
|
||||||
|
|
||||||
|
ShoppingCart.TotalPrice = gross + ShoppingCart.TotalVat;
|
||||||
|
|
||||||
|
return ShoppingCart.TotalPrice;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
namespace MidrandBookshop.Services.ShoppingCart.Models;
|
||||||
|
|
||||||
|
public sealed class Cart
|
||||||
|
{
|
||||||
|
public long? CustomerId { get; set; }
|
||||||
|
|
||||||
|
public long? OrderId { get; set; }
|
||||||
|
|
||||||
|
public decimal TotalPrice { get; set; }
|
||||||
|
|
||||||
|
public decimal TotalVat { get; set; }
|
||||||
|
|
||||||
|
public decimal VatRate { get; set; } = 0.15m;
|
||||||
|
|
||||||
|
public bool IsVatInclusive { get; set; } = true;
|
||||||
|
|
||||||
|
public IList<CartItem> Items { get; set; } = [];
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
using LiteCharms.Features.MidrandBooks.Products.Models;
|
||||||
|
|
||||||
|
namespace MidrandBookshop.Services.ShoppingCart.Models;
|
||||||
|
|
||||||
|
public sealed class CartItem
|
||||||
|
{
|
||||||
|
public ProductPrice? Price { get; set; }
|
||||||
|
|
||||||
|
public long Quantity { get; set; }
|
||||||
|
|
||||||
|
public decimal Amount { get; set; }
|
||||||
|
}
|
||||||
@@ -101,12 +101,12 @@ spec:
|
|||||||
- name: LiteCharmsSettings__ClientId
|
- name: LiteCharmsSettings__ClientId
|
||||||
valueFrom:
|
valueFrom:
|
||||||
secretKeyRef:
|
secretKeyRef:
|
||||||
name: midrandbooksapi-secrets
|
name: midrandbooks-secrets
|
||||||
key: litecharms-clientid
|
key: litecharms-clientid
|
||||||
- name: LiteCharmsSettings__ClientSecret
|
- name: LiteCharmsSettings__ClientSecret
|
||||||
valueFrom:
|
valueFrom:
|
||||||
secretKeyRef:
|
secretKeyRef:
|
||||||
name: midrandbooksapi-secrets
|
name: midrandbooks-secrets
|
||||||
key: litecharms-clientsecret
|
key: litecharms-clientsecret
|
||||||
- name: BookshopS3Settings__AccessKey
|
- name: BookshopS3Settings__AccessKey
|
||||||
valueFrom:
|
valueFrom:
|
||||||
|
|||||||
Reference in New Issue
Block a user