diff --git a/ShopOnline.Api/Controllers/ProductController.cs b/ShopOnline.Api/Controllers/ProductController.cs index 2010bc2..b666834 100644 --- a/ShopOnline.Api/Controllers/ProductController.cs +++ b/ShopOnline.Api/Controllers/ProductController.cs @@ -21,10 +21,10 @@ namespace ShopOnline.Api.Controllers try { var products = await this.productRepository.GetItems(); - //foreach (var product in products) - //{ - // Console.WriteLine("Product [" + product.Id + "]: " + product.Price + " / " + product.Quantity); - //} + foreach (var product in products) + { + Console.WriteLine("Product [" + product.Id + "]: " + product.Price + " / " + product.Quantity); + } var productCategories = await this.productRepository.GetCategories(); if (products == null || productCategories == null) diff --git a/ShopOnline.Api/Controllers/ShoppingCartController.cs b/ShopOnline.Api/Controllers/ShoppingCartController.cs new file mode 100644 index 0000000..bd3e79a --- /dev/null +++ b/ShopOnline.Api/Controllers/ShoppingCartController.cs @@ -0,0 +1,104 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using ShopOnline.Api.Extensions; +using ShopOnline.Api.Repositories.Contracts; +using ShopOnline.Models.Dtos; + +namespace ShopOnline.Api.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class ShoppingCartController : ControllerBase + { + private readonly IShoppingCartRepository shoppingCartRepository; + private readonly IProductRepository productRepository; + + public ShoppingCartController(IShoppingCartRepository shoppingCartRepository, + IProductRepository productRepository) + { + this.shoppingCartRepository = shoppingCartRepository; + this.productRepository = productRepository; + } + + [HttpGet] + [Route("{userId}/GetItems")] + public async Task>> GetItems(int userId) + { + try + { + var cartItems = await shoppingCartRepository.GetItems(userId); + + if(cartItems == null) + { + return NoContent(); + } + var products = await this.productRepository.GetItems(); + + if(products == null) + { + throw new Exception("No products exist in the system."); + } + + var cartItemsDto = cartItems.ConvertToDto(products); + + return Ok(cartItemsDto); + } + catch (Exception ex) + { + return StatusCode(StatusCodes.Status500InternalServerError, ex.Message); + } + } + + [HttpGet("{id:int}")] + public async Task> GetItem(int id) + { + try + { + var cartItem = await this.shoppingCartRepository.GetItem(id); + if (cartItem == null) + { + return NotFound(); + } + + var product = await productRepository.GetItem(cartItem.ProductId); + if(product == null) + { + return NotFound(); + } + var cartItemDto = cartItem.ConvertToDto(product); + return Ok(cartItemDto); + } + catch(Exception ex) + { + return StatusCode(StatusCodes.Status500InternalServerError, ex.Message); + + } + } + + [HttpPost] + public async Task> PostItem([FromBody] CartItemToAddDto cartItemToAddDto) + { + try + { + var newCartItem = await this.shoppingCartRepository.AddItem(cartItemToAddDto); + if(newCartItem == null) + { + return NoContent(); + } + var product = await productRepository.GetItem(newCartItem.ProductId); + if(product == null) + { + throw new Exception($"Something went wrong when attempting to retrieve product (productID:({cartItemToAddDto.ProductId}))"); + } + + var newCartItemDto = newCartItem.ConvertToDto(product); + return CreatedAtAction(nameof(GetItem), new { id = newCartItemDto.Id }, newCartItemDto); + } + catch (Exception ex) + { + return StatusCode(StatusCodes.Status500InternalServerError, ex.Message); + + } + } + } +} diff --git a/ShopOnline.Api/Extensions/DtoConversions.cs b/ShopOnline.Api/Extensions/DtoConversions.cs index 3bab04a..2d2ad06 100644 --- a/ShopOnline.Api/Extensions/DtoConversions.cs +++ b/ShopOnline.Api/Extensions/DtoConversions.cs @@ -40,5 +40,40 @@ namespace ShopOnline.Api.Extensions }; } + public static IEnumerable ConvertToDto(this IEnumerable cartItems, IEnumerable products) + { + return (from cartItem in cartItems + join product in products + on cartItem.ProductId equals product.Id + select new CartItemDto + { + Id = cartItem.Id, + ProductId = cartItem.ProductId, + ProductName = product.Name, + ProductDescription = product.Description, + ProductImageURL = product.ImageURL, + Price = product.Price, + CartId = cartItem.CartId, + Quantity = cartItem.Quantity, + TotalPrice = product.Price * cartItem.Quantity + }).ToList(); + } + + public static CartItemDto ConvertToDto(this CartItem cartItem, Product product) + { + return new CartItemDto + { + Id = cartItem.Id, + ProductId = cartItem.ProductId, + ProductName = product.Name, + ProductDescription = product.Description, + ProductImageURL = product.ImageURL, + Price = product.Price, + CartId = cartItem.CartId, + Quantity = cartItem.Quantity, + TotalPrice = product.Price * cartItem.Quantity + }; + } } + } diff --git a/ShopOnline.Api/Program.cs b/ShopOnline.Api/Program.cs index 86b0c16..b395440 100644 --- a/ShopOnline.Api/Program.cs +++ b/ShopOnline.Api/Program.cs @@ -18,6 +18,7 @@ builder.Services.AddDbContextPool(options => ); builder.Services.AddScoped(); +builder.Services.AddScoped(); var app = builder.Build(); diff --git a/ShopOnline.Api/Repositories/Contracts/IShoppingCartRepository.cs b/ShopOnline.Api/Repositories/Contracts/IShoppingCartRepository.cs new file mode 100644 index 0000000..e147673 --- /dev/null +++ b/ShopOnline.Api/Repositories/Contracts/IShoppingCartRepository.cs @@ -0,0 +1,15 @@ +using ShopOnline.Api.Entities; +using ShopOnline.Models.Dtos; + +namespace ShopOnline.Api.Repositories.Contracts +{ + public interface IShoppingCartRepository + { + Task + AddItem(CartItemToAddDto cartItemToAddDto); + Task UpdateQuantity(int id, CartItemQuantityUpdateDto cartItemQuantityUpdateDto); + Task DeleteItem(int id); + Task GetItem(int id); + Task> GetItems(int userId); + } +} diff --git a/ShopOnline.Api/Repositories/ShoppingCartRepository.cs b/ShopOnline.Api/Repositories/ShoppingCartRepository.cs new file mode 100644 index 0000000..6b3fd95 --- /dev/null +++ b/ShopOnline.Api/Repositories/ShoppingCartRepository.cs @@ -0,0 +1,88 @@ +using Microsoft.EntityFrameworkCore; +using ShopOnline.Api.Data; +using ShopOnline.Api.Entities; +using ShopOnline.Api.Repositories.Contracts; +using ShopOnline.Models.Dtos; + +namespace ShopOnline.Api.Repositories +{ + public class ShoppingCartRepository : IShoppingCartRepository + { + private readonly ShopOnlineDbContext shopOnlineDbContext; + + public ShoppingCartRepository(ShopOnlineDbContext shopOnlineDbContext) + { + this.shopOnlineDbContext = shopOnlineDbContext; + } + + private async Task CartItemExists(int cartId, int productId) + { + return await this.shopOnlineDbContext.CartItems.AnyAsync(c => c.CartId == cartId && c.ProductId == productId); + } + public async Task AddItem(CartItemToAddDto cartItemToAddDto) + { + if(await CartItemExists(cartItemToAddDto.CartId, cartItemToAddDto.ProductId) == false) + { + var item = await (from product in this.shopOnlineDbContext.Products + where product.Id == cartItemToAddDto.ProductId + select new CartItem + { + CartId = cartItemToAddDto.CartId, + ProductId = product.Id, + Quantity = cartItemToAddDto.Quantity, + }).SingleOrDefaultAsync(); + + if(item != null) + { + var result = await this.shopOnlineDbContext.CartItems.AddAsync(item); + await this.shopOnlineDbContext.SaveChangesAsync(); + return result.Entity; + } + } + + return null; + + } + + + public Task DeleteItem(int id) + { + throw new NotImplementedException(); + } + + public async Task GetItem(int id) + { + return await (from cart in this.shopOnlineDbContext.Carts + join cartItem in this.shopOnlineDbContext.CartItems + on cart.Id equals cartItem.CartId + where cartItem.Id == id + select new CartItem + { + Id = cartItem.Id, + ProductId = cartItem.ProductId, + Quantity = cartItem.Quantity, + CartId = cartItem.CartId + }).SingleOrDefaultAsync(); + } + + public async Task> GetItems(int userId) + { + return await (from cart in this.shopOnlineDbContext.Carts + join cartItem in this.shopOnlineDbContext.CartItems + on cart.Id equals cartItem.CartId + where cart.UserId == userId + select new CartItem + { + Id = cartItem.Id, + ProductId = cartItem.ProductId, + Quantity = cartItem.Quantity, + CartId = cartItem.CartId + }).ToListAsync(); + } + + public Task UpdateQuantity(int id, CartItemQuantityUpdateDto cartItemQuantityUpdateDto) + { + throw new NotImplementedException(); + } + } +} diff --git a/ShopOnline.Web/HardCoded.cs b/ShopOnline.Web/HardCoded.cs new file mode 100644 index 0000000..69040c5 --- /dev/null +++ b/ShopOnline.Web/HardCoded.cs @@ -0,0 +1,8 @@ +namespace ShopOnline.Web +{ + public static class HardCoded + { + public const int UserId = 1; + public const int CartId = 1; + } +} diff --git a/ShopOnline.Web/Pages/DisplaySpinner.razor.css b/ShopOnline.Web/Pages/DisplaySpinner.razor.css index 1f5a19a..f2f4639 100644 --- a/ShopOnline.Web/Pages/DisplaySpinner.razor.css +++ b/ShopOnline.Web/Pages/DisplaySpinner.razor.css @@ -9,7 +9,7 @@ height: 64px; margin: 8px; border-radius: 50%; - background: black; + background: #ffde00; animation: lds-circle 2.4s cubic-bezier(0, 0.2, 0.8, 1) infinite; } diff --git a/ShopOnline.Web/Pages/ProductDetails.razor b/ShopOnline.Web/Pages/ProductDetails.razor index 52c7d07..867265f 100644 --- a/ShopOnline.Web/Pages/ProductDetails.razor +++ b/ShopOnline.Web/Pages/ProductDetails.razor @@ -22,6 +22,14 @@ else

@Product.Price.ToString("C") (@Product.Quantity in stock)

+
+ + +
} \ No newline at end of file diff --git a/ShopOnline.Web/Pages/ProductDetailsBase.cs b/ShopOnline.Web/Pages/ProductDetailsBase.cs index 9f05778..272cca8 100644 --- a/ShopOnline.Web/Pages/ProductDetailsBase.cs +++ b/ShopOnline.Web/Pages/ProductDetailsBase.cs @@ -11,6 +11,10 @@ namespace ShopOnline.Web.Pages public int Id { get; set; } [Inject] public IProductService ProductService { get; set; } + [Inject] + public IShoppingCartService ShoppingCartService { get; set; } + [Inject] + public NavigationManager NavigationManager { get; set; } public ProductDto Product { get; set; } public string ErrorMessage { get; set; } @@ -25,5 +29,19 @@ namespace ShopOnline.Web.Pages ErrorMessage = ex.Message; } } + + protected async Task AddToCart_Click(CartItemToAddDto cartItemToAddDto) + { + try + { + var cartItemDto = await ShoppingCartService.AddItem(cartItemToAddDto); + NavigationManager.NavigateTo("/ShoppingCart"); + } + catch (Exception) + { + + } + } + } } diff --git a/ShopOnline.Web/Pages/ShoppingCart.razor b/ShopOnline.Web/Pages/ShoppingCart.razor new file mode 100644 index 0000000..862edfe --- /dev/null +++ b/ShopOnline.Web/Pages/ShoppingCart.razor @@ -0,0 +1,39 @@ +@page "/ShoppingCart" +@inherits ShoppingCartBase + +@if(ShoppingCartItems == null && ErrorMessage == null) { + +} +else if(ErrorMessage != null) { + +} +else { +

Shopping Cart

+
+
+ @foreach(var item in ShoppingCartItems) { +
+
+ +
+
+
@item.ProductName
+
@item.ProductDescription
+ Price: @item.Price.ToString("C") +
+
+ } +
+
+
Cart Summary
+ +
+
+ + +} \ No newline at end of file diff --git a/ShopOnline.Web/Pages/ShoppingCartBase.cs b/ShopOnline.Web/Pages/ShoppingCartBase.cs new file mode 100644 index 0000000..a7b9c65 --- /dev/null +++ b/ShopOnline.Web/Pages/ShoppingCartBase.cs @@ -0,0 +1,29 @@ +using Microsoft.AspNetCore.Components; +using ShopOnline.Models.Dtos; +using ShopOnline.Web.Services.Contracts; + +namespace ShopOnline.Web.Pages +{ + public class ShoppingCartBase:ComponentBase + { + [Inject] + public IShoppingCartService ShoppingCartService { get; set; } + + public IEnumerable ShoppingCartItems { get; set; } + + public string ErrorMessage { get; set; } + + protected override async Task OnInitializedAsync() + { + try + { + ShoppingCartItems = await ShoppingCartService.GetItems(HardCoded.UserId); + } + catch (Exception ex) + { + ErrorMessage = ex.Message; + } + } + + } +} diff --git a/ShopOnline.Web/Program.cs b/ShopOnline.Web/Program.cs index b38dc6f..f63bae7 100644 --- a/ShopOnline.Web/Program.cs +++ b/ShopOnline.Web/Program.cs @@ -10,5 +10,6 @@ builder.RootComponents.Add("head::after"); builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri("https://localhost:7125/") }); builder.Services.AddScoped(); +builder.Services.AddScoped(); await builder.Build().RunAsync(); diff --git a/ShopOnline.Web/Services/Contracts/IShoppingCartService.cs b/ShopOnline.Web/Services/Contracts/IShoppingCartService.cs new file mode 100644 index 0000000..557612a --- /dev/null +++ b/ShopOnline.Web/Services/Contracts/IShoppingCartService.cs @@ -0,0 +1,10 @@ +using ShopOnline.Models.Dtos; + +namespace ShopOnline.Web.Services.Contracts +{ + public interface IShoppingCartService + { + Task> GetItems(int userId); + Task AddItem(CartItemToAddDto cartItemToAddDto); + } +} diff --git a/ShopOnline.Web/Services/ShoppingCartService.cs b/ShopOnline.Web/Services/ShoppingCartService.cs new file mode 100644 index 0000000..aa8014d --- /dev/null +++ b/ShopOnline.Web/Services/ShoppingCartService.cs @@ -0,0 +1,67 @@ +using ShopOnline.Models.Dtos; +using ShopOnline.Web.Services.Contracts; +using System.Net.Http.Json; + +namespace ShopOnline.Web.Services +{ + public class ShoppingCartService : IShoppingCartService + { + private readonly HttpClient httpClient; + + public ShoppingCartService(HttpClient httpClient) + { + this.httpClient = httpClient; + } + public async Task AddItem(CartItemToAddDto cartItemToAddDto) + { + try + { + var response = await httpClient.PostAsJsonAsync("api/ShoppingCart", cartItemToAddDto); + + if (response.IsSuccessStatusCode) + { + if(response.StatusCode == System.Net.HttpStatusCode.NoContent) + { + return default(CartItemDto); + } + return await response.Content.ReadFromJsonAsync(); + } + else + { + var message = await response.Content.ReadAsStringAsync(); + throw new Exception($"Http status: {response.StatusCode}\n\tMessage: {message}"); + } + } + catch + { + throw; + } + } + + public async Task> GetItems(int userId) + { + try + { + var response = await httpClient.GetAsync($"api/ShoppingCart/{userId}/GetItems"); + if(response.IsSuccessStatusCode) + { + if(response.StatusCode == System.Net.HttpStatusCode.NoContent) + { + return Enumerable.Empty(); + } + return await response.Content.ReadFromJsonAsync>(); + } + else + { + var message = await response.Content.ReadAsStringAsync(); + throw new Exception($"Http status: {response.StatusCode}\n\tMessage: {message}"); + + } + } + catch(Exception) + { + throw; + } + } + } +} diff --git a/ShopOnlineSolution.sln b/ShopOnlineSolution.sln index 2c30cfd..0b0f0cc 100644 --- a/ShopOnlineSolution.sln +++ b/ShopOnlineSolution.sln @@ -7,7 +7,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ShopOnline.Web", "ShopOnlin EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ShopOnline.Api", "ShopOnline.Api\ShopOnline.Api.csproj", "{B1FE41C0-D80F-414D-AE0F-3164789CF796}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ShopOnline.Models", "ShopOnline.Models\ShopOnline.Models.csproj", "{C0674431-3C6E-4F51-8A5C-0523243EB33D}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ShopOnline.Models", "ShopOnline.Models\ShopOnline.Models.csproj", "{C0674431-3C6E-4F51-8A5C-0523243EB33D}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution