Adding Order Features (#47)
* Working on order model binding from checkout page - WIP * Small layout tweaks (#43) * Updating quantities implemented. * Fixed basket widget count
This commit is contained in:
@@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Identity;
|
||||
using Infrastructure.Identity;
|
||||
using System;
|
||||
using Web;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.eShopWeb.Controllers
|
||||
{
|
||||
@@ -17,14 +18,17 @@ namespace Microsoft.eShopWeb.Controllers
|
||||
private const string _basketSessionKey = "basketId";
|
||||
private readonly IUriComposer _uriComposer;
|
||||
private readonly SignInManager<ApplicationUser> _signInManager;
|
||||
private readonly IAppLogger<BasketController> _logger;
|
||||
|
||||
public BasketController(IBasketService basketService,
|
||||
IUriComposer uriComposer,
|
||||
SignInManager<ApplicationUser> signInManager)
|
||||
SignInManager<ApplicationUser> signInManager,
|
||||
IAppLogger<BasketController> logger)
|
||||
{
|
||||
_basketService = basketService;
|
||||
_uriComposer = uriComposer;
|
||||
_signInManager = signInManager;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
@@ -35,6 +39,16 @@ namespace Microsoft.eShopWeb.Controllers
|
||||
return View(basketModel);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Index(Dictionary<string, int> items)
|
||||
{
|
||||
var basketViewModel = await GetBasketViewModelAsync();
|
||||
await _basketService.SetQuantities(basketViewModel.Id, items);
|
||||
|
||||
return View(await GetBasketViewModelAsync());
|
||||
}
|
||||
|
||||
|
||||
// POST: /Basket/AddToBasket
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> AddToBasket(CatalogItemViewModel productDetails)
|
||||
@@ -51,9 +65,17 @@ namespace Microsoft.eShopWeb.Controllers
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Checkout()
|
||||
public async Task<IActionResult> Checkout(List<BasketItemViewModel> model)
|
||||
{
|
||||
// TODO: Get model binding working with collection of items
|
||||
var basket = await GetBasketViewModelAsync();
|
||||
//await _basketService.SetQuantities(basket.Id, quantities);
|
||||
|
||||
foreach (var item in basket.Items)
|
||||
{
|
||||
_logger.LogWarning($"Id: {item.Id}; Qty: {item.Quantity}");
|
||||
}
|
||||
// redirect to OrdersController
|
||||
|
||||
await _basketService.Checkout(basket.Id);
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using Microsoft.eShopWeb.ViewModels;
|
||||
using Microsoft.eShopWeb.ApplicationCore.Entities;
|
||||
using Microsoft.eShopWeb.ViewModels;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ApplicationCore.Interfaces
|
||||
@@ -8,6 +10,7 @@ namespace ApplicationCore.Interfaces
|
||||
Task<BasketViewModel> GetOrCreateBasketForUser(string userName);
|
||||
Task TransferBasketAsync(string anonymousId, string userName);
|
||||
Task AddItemToBasket(int basketId, int catalogItemId, decimal price, int quantity);
|
||||
Task SetQuantities(int basketId, Dictionary<string, int> quantities);
|
||||
Task Checkout(int basketId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,14 +12,17 @@ namespace Web.Services
|
||||
{
|
||||
private readonly IAsyncRepository<Basket> _basketRepository;
|
||||
private readonly IUriComposer _uriComposer;
|
||||
private readonly IAppLogger<BasketService> _logger;
|
||||
private readonly IRepository<CatalogItem> _itemRepository;
|
||||
|
||||
public BasketService(IAsyncRepository<Basket> basketRepository,
|
||||
IRepository<CatalogItem> itemRepository,
|
||||
IUriComposer uriComposer)
|
||||
IUriComposer uriComposer,
|
||||
IAppLogger<BasketService> logger)
|
||||
{
|
||||
_basketRepository = basketRepository;
|
||||
_uriComposer = uriComposer;
|
||||
this._logger = logger;
|
||||
_itemRepository = itemRepository;
|
||||
}
|
||||
|
||||
@@ -81,6 +84,20 @@ namespace Web.Services
|
||||
await _basketRepository.UpdateAsync(basket);
|
||||
}
|
||||
|
||||
public async Task SetQuantities(int basketId, Dictionary<string,int> quantities)
|
||||
{
|
||||
var basket = await _basketRepository.GetByIdAsync(basketId);
|
||||
foreach (var item in basket.Items)
|
||||
{
|
||||
if (quantities.TryGetValue(item.Id.ToString(), out var quantity))
|
||||
{
|
||||
_logger.LogWarning($"Updating quantity of item ID:{item.Id} to {quantity}.");
|
||||
item.Quantity = quantity;
|
||||
}
|
||||
}
|
||||
await _basketRepository.UpdateAsync(basket);
|
||||
}
|
||||
|
||||
public async Task Checkout(int basketId)
|
||||
{
|
||||
var basket = await _basketRepository.GetByIdAsync(basketId);
|
||||
|
||||
@@ -1,27 +1,52 @@
|
||||
using ApplicationCore.Interfaces;
|
||||
using Infrastructure.Identity;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.eShopWeb.ViewModels;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Web.ViewComponents
|
||||
{
|
||||
public class Basket : ViewComponent
|
||||
{
|
||||
private readonly IBasketService _cartSvc;
|
||||
private readonly IBasketService _basketService;
|
||||
private readonly SignInManager<ApplicationUser> _signInManager;
|
||||
|
||||
public Basket(IBasketService cartSvc) => _cartSvc = cartSvc;
|
||||
public Basket(IBasketService basketService,
|
||||
SignInManager<ApplicationUser> signInManager)
|
||||
{
|
||||
_basketService = basketService;
|
||||
_signInManager = signInManager;
|
||||
}
|
||||
|
||||
public async Task<IViewComponentResult> InvokeAsync(string userName)
|
||||
{
|
||||
var vm = new BasketComponentViewModel();
|
||||
var itemsInCart = await ItemsInBasketAsync(userName);
|
||||
vm.ItemsCount = itemsInCart;
|
||||
vm.ItemsCount = (await GetBasketViewModelAsync()).Items.Sum(i => i.Quantity);
|
||||
return View(vm);
|
||||
}
|
||||
private async Task<int> ItemsInBasketAsync(string userName)
|
||||
|
||||
private async Task<BasketViewModel> GetBasketViewModelAsync()
|
||||
{
|
||||
var basket = await _cartSvc.GetOrCreateBasketForUser(userName);
|
||||
return basket.Items.Count;
|
||||
if (_signInManager.IsSignedIn(HttpContext.User))
|
||||
{
|
||||
return await _basketService.GetOrCreateBasketForUser(User.Identity.Name);
|
||||
}
|
||||
string anonymousId = GetBasketIdFromCookie();
|
||||
if (anonymousId == null) return new BasketViewModel();
|
||||
return await _basketService.GetOrCreateBasketForUser(anonymousId);
|
||||
}
|
||||
|
||||
private string GetBasketIdFromCookie()
|
||||
{
|
||||
if (Request.Cookies.ContainsKey(Constants.BASKET_COOKIENAME))
|
||||
{
|
||||
return Request.Cookies[Constants.BASKET_COOKIENAME];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
@using Microsoft.eShopWeb.ViewModels
|
||||
@model BasketViewModel
|
||||
@{
|
||||
ViewData["Title"] = "Basket";
|
||||
@model BasketViewModel
|
||||
}
|
||||
<section class="esh-catalog-hero">
|
||||
<div class="container">
|
||||
@@ -23,9 +23,10 @@
|
||||
<section class="esh-basket-title col-xs-2">Cost</section>
|
||||
</article>
|
||||
<div class="esh-catalog-items row">
|
||||
@foreach (var item in Model.Items)
|
||||
@for (int i=0; i< Model.Items.Count; i++)
|
||||
{
|
||||
<article class="esh-basket-items row">
|
||||
var item = Model.Items[i];
|
||||
<article class="esh-basket-items row">
|
||||
<div>
|
||||
<section class="esh-basket-item esh-basket-item--middle col-lg-3 hidden-lg-down">
|
||||
<img class="esh-basket-image" src="@item.PictureUrl" />
|
||||
@@ -33,8 +34,8 @@
|
||||
<section class="esh-basket-item esh-basket-item--middle col-xs-3">@item.ProductName</section>
|
||||
<section class="esh-basket-item esh-basket-item--middle col-xs-2">$ @item.UnitPrice.ToString("N2")</section>
|
||||
<section class="esh-basket-item esh-basket-item--middle col-xs-2">
|
||||
<input type="hidden" name="@("quantities[" + item.Id +"].Key")" value="@item.Id" />
|
||||
<input type="number" class="esh-basket-input" min="1" name="@("quantities[" + item.Id +"].Value")" value="@item.Quantity" />
|
||||
<input type="hidden" name="@("Items[" + i + "].Key")" value="@item.Id" />
|
||||
<input type="number" class="esh-basket-input" min="1" name="@("Items[" + i + "].Value")" value="@item.Quantity" />
|
||||
</section>
|
||||
<section class="esh-basket-item esh-basket-item--middle esh-basket-item--mark col-xs-2">$ @Math.Round(item.Quantity * item.UnitPrice, 2).ToString("N2")</section>
|
||||
</div>
|
||||
@@ -65,7 +66,9 @@
|
||||
</article>
|
||||
</div>
|
||||
}
|
||||
<section class="esh-basket-item col-xs-push-9 col-xs-3">
|
||||
<section class="esh-basket-item col-xs-push-8 col-xs-4">
|
||||
<button class="btn esh-basket-checkout" name="updatebutton" value="" type="submit"
|
||||
asp-action="Update">[ Update ]</button>
|
||||
<input type="submit" asp-action="Checkout"
|
||||
class="btn esh-basket-checkout"
|
||||
value="[ Checkout ]" name="action" />
|
||||
|
||||
@@ -1,32 +1,38 @@
|
||||
@model PaginationInfoViewModel
|
||||
|
||||
<div class="esh-pager">
|
||||
<div class="container">
|
||||
<article class="esh-pager-wrapper row">
|
||||
<nav>
|
||||
<a class="esh-pager-item esh-pager-item--navigable @Model.Previous"
|
||||
id="Previous"
|
||||
asp-controller="Catalog"
|
||||
asp-action="Index"
|
||||
asp-route-page="@(Model.ActualPage -1)"
|
||||
aria-label="Previous">
|
||||
Previous
|
||||
</a>
|
||||
|
||||
<span class="esh-pager-item">
|
||||
Showing @Model.ItemsPerPage of @Model.TotalItems products - Page @(Model.ActualPage + 1) - @Model.TotalPages
|
||||
</span>
|
||||
|
||||
<a class="esh-pager-item esh-pager-item--navigable @Model.Next"
|
||||
id="Next"
|
||||
asp-controller="Catalog"
|
||||
asp-action="Index"
|
||||
asp-route-page="@(Model.ActualPage + 1)"
|
||||
aria-label="Next">
|
||||
Next
|
||||
</a>
|
||||
</nav>
|
||||
</article>
|
||||
</div>
|
||||
<div class="container-fluid">
|
||||
<article class="esh-pager-wrapper row">
|
||||
<nav>
|
||||
<div class="col-md-2 col-xs-12">
|
||||
<a class="esh-pager-item-left esh-pager-item--navigable @Model.Previous"
|
||||
id="Previous"
|
||||
asp-controller="Catalog"
|
||||
asp-action="Index"
|
||||
asp-route-page="@(Model.ActualPage - 1)"
|
||||
aria-label="Previous">
|
||||
Previous
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="col-md-8 col-xs-12">
|
||||
<span class="esh-pager-item">
|
||||
Showing @Model.ItemsPerPage of @Model.TotalItems products - Page @(Model.ActualPage + 1) - @Model.TotalPages
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="col-md-2 col-xs-12">
|
||||
<a class="esh-pager-item-right esh-pager-item--navigable @Model.Next"
|
||||
id="Next"
|
||||
asp-controller="Catalog"
|
||||
asp-action="Index"
|
||||
asp-route-page="@(Model.ActualPage + 1)"
|
||||
aria-label="Next">
|
||||
Next
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,89 +1,79 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>@ViewData["Title"] - Microsoft.eShopOnWeb</title>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>@ViewData["Title"] - Microsoft.eShopOnWeb</title>
|
||||
|
||||
<environment names="Development">
|
||||
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
|
||||
<environment names="Development">
|
||||
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
|
||||
<link rel="stylesheet" href="~/css/app.css" />
|
||||
<link rel="stylesheet" href="~/css/app.component.css" />
|
||||
@*<link rel="stylesheet" href="~/css/shared/components/header/header.css" />
|
||||
<link rel="stylesheet" href="~/css/shared/components/identity/identity.css" />
|
||||
<link rel="stylesheet" href="~/css/campaigns/campaigns.component.css" />
|
||||
<link rel="stylesheet" href="~/css/shared/components/pager/pager.css" />*@
|
||||
<link rel="stylesheet" href="~/css/basket/basket.component.css" />
|
||||
<link rel="stylesheet" href="~/css/basket/basket-status/basket-status.component.css" />
|
||||
<link rel="stylesheet" href="~/css/catalog/catalog.component.css" />
|
||||
@*<link rel="stylesheet" href="~/css/orders/orders.component.css" />
|
||||
<link rel="stylesheet" href="~/css/orders/orders-detail/orders-detail.component.css" />
|
||||
<link rel="stylesheet" href="~/css/orders/orders-new/orders-new.component.css" />
|
||||
<link rel="stylesheet" href="~/css/override.css" type="text/css" />*@
|
||||
</environment>
|
||||
<environment names="Staging,Production">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha.5/css/bootstrap.min.css"
|
||||
asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"
|
||||
asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute" />
|
||||
<link rel="stylesheet" href="~/css/app.min.css" asp-append-version="true" />
|
||||
</environment>
|
||||
<environment names="Staging,Production">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha.5/css/bootstrap.min.css"
|
||||
asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"
|
||||
asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute" />
|
||||
<link rel="stylesheet" href="~/css/app.min.css" asp-append-version="true" />
|
||||
</environment>
|
||||
<link rel="stylesheet" href="~/css/app.component.css" />
|
||||
<link rel="stylesheet" href="~/css/basket/basket.component.css" />
|
||||
<link rel="stylesheet" href="~/css/catalog/pager.css" />
|
||||
<link rel="stylesheet" href="~/css/catalog/catalog.component.css" />
|
||||
<link rel="stylesheet" href="~/css/catalog/catalog.component.css" />
|
||||
<link rel="stylesheet" href="~/css/basket/basket-status/basket-status.component.css" />
|
||||
</head>
|
||||
<body>
|
||||
<header class="navbar navbar-light navbar-static-top">
|
||||
<div class="container">
|
||||
<article class="row">
|
||||
<header class="navbar navbar-light navbar-static-top">
|
||||
<div class="container">
|
||||
<article class="row">
|
||||
|
||||
<section class="col-lg-7 col-md-6 col-xs-12">
|
||||
<a class="navbar-brand" routerLink="catalog">
|
||||
<a asp-area="" asp-controller="Catalog" asp-action="Index">
|
||||
<img src="../images/brand.png" />
|
||||
</a>
|
||||
</section>
|
||||
@await Html.PartialAsync("_LoginPartial")
|
||||
<section class="col-lg-1 col-xs-12"><a asp-controller="Basket" asp-action="Index">Basket</a></section>
|
||||
<section class="col-lg-7 col-md-6 col-xs-12">
|
||||
<a asp-area="" asp-controller="Catalog" asp-action="Index" class="navbar-brand">
|
||||
<img src="../images/brand.png" alt="eShop On Web"/>
|
||||
</a>
|
||||
</section>
|
||||
@await Html.PartialAsync("_LoginPartial")
|
||||
<section class="col-lg-1 col-md-3 col-xs-6"><a asp-controller="Basket" asp-action="Index">Basket</a></section>
|
||||
|
||||
</article>
|
||||
</div>
|
||||
</header>
|
||||
</article>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@RenderBody()
|
||||
@RenderBody()
|
||||
|
||||
|
||||
<footer class="esh-app-footer">
|
||||
<div class="container">
|
||||
<article class="row">
|
||||
<footer class="esh-app-footer">
|
||||
<div class="container">
|
||||
<article class="row">
|
||||
|
||||
<section class="col-sm-6">
|
||||
<img class="esh-app-footer-brand" src="../images/brand_dark.png" />
|
||||
</section>
|
||||
<section class="col-sm-6">
|
||||
<img class="esh-app-footer-brand" src="../images/brand_dark.png" />
|
||||
</section>
|
||||
|
||||
<section class="col-sm-6">
|
||||
<div class="esh-app-footer-text hidden-xs"> e-ShopOnWeb. All right reserved </div>
|
||||
</section>
|
||||
<section class="col-sm-6">
|
||||
<div class="esh-app-footer-text hidden-xs"> e-ShopOnWeb. All right reserved </div>
|
||||
</section>
|
||||
|
||||
</article>
|
||||
</div>
|
||||
</footer>
|
||||
</article>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<environment names="Development">
|
||||
<script src="~/lib/jquery/dist/jquery.js"></script>
|
||||
<script src="~/lib/bootstrap/dist/js/bootstrap.js"></script>
|
||||
<script src="~/js/site.js" asp-append-version="true"></script>
|
||||
</environment>
|
||||
<environment names="Staging,Production">
|
||||
<script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-2.2.0.min.js"
|
||||
asp-fallback-src="~/lib/jquery/dist/jquery.min.js"
|
||||
asp-fallback-test="window.jQuery">
|
||||
</script>
|
||||
<script src="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.6/bootstrap.min.js"
|
||||
asp-fallback-src="~/lib/bootstrap/dist/js/bootstrap.min.js"
|
||||
asp-fallback-test="window.jQuery && window.jQuery.fn && window.jQuery.fn.modal">
|
||||
</script>
|
||||
<script src="~/js/site.min.js" asp-append-version="true"></script>
|
||||
</environment>
|
||||
<environment names="Development">
|
||||
<script src="~/lib/jquery/dist/jquery.js"></script>
|
||||
<script src="~/lib/bootstrap/dist/js/bootstrap.js"></script>
|
||||
<script src="~/js/site.js" asp-append-version="true"></script>
|
||||
</environment>
|
||||
<environment names="Staging,Production">
|
||||
<script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-2.2.0.min.js"
|
||||
asp-fallback-src="~/lib/jquery/dist/jquery.min.js"
|
||||
asp-fallback-test="window.jQuery">
|
||||
</script>
|
||||
<script src="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.6/bootstrap.min.js"
|
||||
asp-fallback-src="~/lib/bootstrap/dist/js/bootstrap.min.js"
|
||||
asp-fallback-test="window.jQuery && window.jQuery.fn && window.jQuery.fn.modal">
|
||||
</script>
|
||||
<script src="~/js/site.min.js" asp-append-version="true"></script>
|
||||
</environment>
|
||||
|
||||
@RenderSection("scripts", required: false)
|
||||
@RenderSection("scripts", required: false)
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
<section class="col-lg-4 col-md-5 col-xs-12">
|
||||
<section class="col-lg-1 col-lg-offset-3 col-md-3 col-xs-6">
|
||||
<div class="esh-identity">
|
||||
<section class="esh-identity-section">
|
||||
<div class="esh-identity-item">
|
||||
@@ -52,6 +52,7 @@ else
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="col-lg-1 col-xs-12">@await Component.InvokeAsync("Basket")
|
||||
</section>
|
||||
<section class="col-lg-1 col-xs-12">
|
||||
@await Component.InvokeAsync("Basket")
|
||||
</section>
|
||||
}
|
||||
|
||||
@@ -3,8 +3,12 @@
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.esh-pager-item {
|
||||
margin: 0 5vw;
|
||||
.esh-pager-item-left {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.esh-pager-item-right {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.esh-pager-item--navigable {
|
||||
|
||||
Reference in New Issue
Block a user