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:
Steve Smith
2017-09-13 18:10:19 -04:00
committed by GitHub
parent ed5d17672d
commit db6ad75aee
9 changed files with 188 additions and 117 deletions

View File

@@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Identity;
using Infrastructure.Identity; using Infrastructure.Identity;
using System; using System;
using Web; using Web;
using System.Collections.Generic;
namespace Microsoft.eShopWeb.Controllers namespace Microsoft.eShopWeb.Controllers
{ {
@@ -17,14 +18,17 @@ namespace Microsoft.eShopWeb.Controllers
private const string _basketSessionKey = "basketId"; private const string _basketSessionKey = "basketId";
private readonly IUriComposer _uriComposer; private readonly IUriComposer _uriComposer;
private readonly SignInManager<ApplicationUser> _signInManager; private readonly SignInManager<ApplicationUser> _signInManager;
private readonly IAppLogger<BasketController> _logger;
public BasketController(IBasketService basketService, public BasketController(IBasketService basketService,
IUriComposer uriComposer, IUriComposer uriComposer,
SignInManager<ApplicationUser> signInManager) SignInManager<ApplicationUser> signInManager,
IAppLogger<BasketController> logger)
{ {
_basketService = basketService; _basketService = basketService;
_uriComposer = uriComposer; _uriComposer = uriComposer;
_signInManager = signInManager; _signInManager = signInManager;
_logger = logger;
} }
[HttpGet] [HttpGet]
@@ -35,6 +39,16 @@ namespace Microsoft.eShopWeb.Controllers
return View(basketModel); 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 // POST: /Basket/AddToBasket
[HttpPost] [HttpPost]
public async Task<IActionResult> AddToBasket(CatalogItemViewModel productDetails) public async Task<IActionResult> AddToBasket(CatalogItemViewModel productDetails)
@@ -51,9 +65,17 @@ namespace Microsoft.eShopWeb.Controllers
} }
[HttpPost] [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(); 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); await _basketService.Checkout(basket.Id);

View File

@@ -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; using System.Threading.Tasks;
namespace ApplicationCore.Interfaces namespace ApplicationCore.Interfaces
@@ -8,6 +10,7 @@ namespace ApplicationCore.Interfaces
Task<BasketViewModel> GetOrCreateBasketForUser(string userName); Task<BasketViewModel> GetOrCreateBasketForUser(string userName);
Task TransferBasketAsync(string anonymousId, string userName); Task TransferBasketAsync(string anonymousId, string userName);
Task AddItemToBasket(int basketId, int catalogItemId, decimal price, int quantity); Task AddItemToBasket(int basketId, int catalogItemId, decimal price, int quantity);
Task SetQuantities(int basketId, Dictionary<string, int> quantities);
Task Checkout(int basketId); Task Checkout(int basketId);
} }
} }

View File

@@ -12,14 +12,17 @@ namespace Web.Services
{ {
private readonly IAsyncRepository<Basket> _basketRepository; private readonly IAsyncRepository<Basket> _basketRepository;
private readonly IUriComposer _uriComposer; private readonly IUriComposer _uriComposer;
private readonly IAppLogger<BasketService> _logger;
private readonly IRepository<CatalogItem> _itemRepository; private readonly IRepository<CatalogItem> _itemRepository;
public BasketService(IAsyncRepository<Basket> basketRepository, public BasketService(IAsyncRepository<Basket> basketRepository,
IRepository<CatalogItem> itemRepository, IRepository<CatalogItem> itemRepository,
IUriComposer uriComposer) IUriComposer uriComposer,
IAppLogger<BasketService> logger)
{ {
_basketRepository = basketRepository; _basketRepository = basketRepository;
_uriComposer = uriComposer; _uriComposer = uriComposer;
this._logger = logger;
_itemRepository = itemRepository; _itemRepository = itemRepository;
} }
@@ -81,6 +84,20 @@ namespace Web.Services
await _basketRepository.UpdateAsync(basket); 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) public async Task Checkout(int basketId)
{ {
var basket = await _basketRepository.GetByIdAsync(basketId); var basket = await _basketRepository.GetByIdAsync(basketId);

View File

@@ -1,27 +1,52 @@
using ApplicationCore.Interfaces; using ApplicationCore.Interfaces;
using Infrastructure.Identity;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.eShopWeb.ViewModels; using Microsoft.eShopWeb.ViewModels;
using System;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Web.ViewComponents namespace Web.ViewComponents
{ {
public class Basket : ViewComponent 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) public async Task<IViewComponentResult> InvokeAsync(string userName)
{ {
var vm = new BasketComponentViewModel(); var vm = new BasketComponentViewModel();
var itemsInCart = await ItemsInBasketAsync(userName); vm.ItemsCount = (await GetBasketViewModelAsync()).Items.Sum(i => i.Quantity);
vm.ItemsCount = itemsInCart;
return View(vm); return View(vm);
} }
private async Task<int> ItemsInBasketAsync(string userName)
private async Task<BasketViewModel> GetBasketViewModelAsync()
{ {
var basket = await _cartSvc.GetOrCreateBasketForUser(userName); if (_signInManager.IsSignedIn(HttpContext.User))
return basket.Items.Count; {
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;
} }
} }
} }

View File

@@ -1,7 +1,7 @@
@using Microsoft.eShopWeb.ViewModels @using Microsoft.eShopWeb.ViewModels
@model BasketViewModel
@{ @{
ViewData["Title"] = "Basket"; ViewData["Title"] = "Basket";
@model BasketViewModel
} }
<section class="esh-catalog-hero"> <section class="esh-catalog-hero">
<div class="container"> <div class="container">
@@ -23,9 +23,10 @@
<section class="esh-basket-title col-xs-2">Cost</section> <section class="esh-basket-title col-xs-2">Cost</section>
</article> </article>
<div class="esh-catalog-items row"> <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> <div>
<section class="esh-basket-item esh-basket-item--middle col-lg-3 hidden-lg-down"> <section class="esh-basket-item esh-basket-item--middle col-lg-3 hidden-lg-down">
<img class="esh-basket-image" src="@item.PictureUrl" /> <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-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">$ @item.UnitPrice.ToString("N2")</section>
<section class="esh-basket-item esh-basket-item--middle col-xs-2"> <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="hidden" name="@("Items[" + i + "].Key")" value="@item.Id" />
<input type="number" class="esh-basket-input" min="1" name="@("quantities[" + item.Id +"].Value")" value="@item.Quantity" /> <input type="number" class="esh-basket-input" min="1" name="@("Items[" + i + "].Value")" value="@item.Quantity" />
</section> </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> <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> </div>
@@ -65,7 +66,9 @@
</article> </article>
</div> </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" <input type="submit" asp-action="Checkout"
class="btn esh-basket-checkout" class="btn esh-basket-checkout"
value="[ Checkout ]" name="action" /> value="[ Checkout ]" name="action" />

View File

@@ -1,32 +1,38 @@
@model PaginationInfoViewModel @model PaginationInfoViewModel
<div class="esh-pager"> <div class="esh-pager">
<div class="container"> <div class="container-fluid">
<article class="esh-pager-wrapper row"> <article class="esh-pager-wrapper row">
<nav> <nav>
<a class="esh-pager-item esh-pager-item--navigable @Model.Previous" <div class="col-md-2 col-xs-12">
id="Previous" <a class="esh-pager-item-left esh-pager-item--navigable @Model.Previous"
asp-controller="Catalog" id="Previous"
asp-action="Index" asp-controller="Catalog"
asp-route-page="@(Model.ActualPage -1)" asp-action="Index"
aria-label="Previous"> asp-route-page="@(Model.ActualPage - 1)"
Previous aria-label="Previous">
</a> Previous
</a>
</div>
<span class="esh-pager-item"> <div class="col-md-8 col-xs-12">
Showing @Model.ItemsPerPage of @Model.TotalItems products - Page @(Model.ActualPage + 1) - @Model.TotalPages <span class="esh-pager-item">
</span> Showing @Model.ItemsPerPage of @Model.TotalItems products - Page @(Model.ActualPage + 1) - @Model.TotalPages
</span>
</div>
<a class="esh-pager-item esh-pager-item--navigable @Model.Next" <div class="col-md-2 col-xs-12">
id="Next" <a class="esh-pager-item-right esh-pager-item--navigable @Model.Next"
asp-controller="Catalog" id="Next"
asp-action="Index" asp-controller="Catalog"
asp-route-page="@(Model.ActualPage + 1)" asp-action="Index"
aria-label="Next"> asp-route-page="@(Model.ActualPage + 1)"
Next aria-label="Next">
</a> Next
</nav> </a>
</article> </div>
</div> </nav>
</article>
</div>
</div> </div>

View File

@@ -1,89 +1,79 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - Microsoft.eShopOnWeb</title> <title>@ViewData["Title"] - Microsoft.eShopOnWeb</title>
<environment names="Development"> <environment names="Development">
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" /> <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
<link rel="stylesheet" href="~/css/app.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>
<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/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> </head>
<body> <body>
<header class="navbar navbar-light navbar-static-top"> <header class="navbar navbar-light navbar-static-top">
<div class="container"> <div class="container">
<article class="row"> <article class="row">
<section class="col-lg-7 col-md-6 col-xs-12"> <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" class="navbar-brand">
<a asp-area="" asp-controller="Catalog" asp-action="Index"> <img src="../images/brand.png" alt="eShop On Web"/>
<img src="../images/brand.png" /> </a>
</a> </section>
</section> @await Html.PartialAsync("_LoginPartial")
@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>
<section class="col-lg-1 col-xs-12"><a asp-controller="Basket" asp-action="Index">Basket</a></section>
</article> </article>
</div> </div>
</header> </header>
@RenderBody() @RenderBody()
<footer class="esh-app-footer"> <footer class="esh-app-footer">
<div class="container"> <div class="container">
<article class="row"> <article class="row">
<section class="col-sm-6"> <section class="col-sm-6">
<img class="esh-app-footer-brand" src="../images/brand_dark.png" /> <img class="esh-app-footer-brand" src="../images/brand_dark.png" />
</section> </section>
<section class="col-sm-6"> <section class="col-sm-6">
<div class="esh-app-footer-text hidden-xs"> e-ShopOnWeb. All right reserved </div> <div class="esh-app-footer-text hidden-xs"> e-ShopOnWeb. All right reserved </div>
</section> </section>
</article> </article>
</div> </div>
</footer> </footer>
<environment names="Development"> <environment names="Development">
<script src="~/lib/jquery/dist/jquery.js"></script> <script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.js"></script> <script src="~/lib/bootstrap/dist/js/bootstrap.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script> <script src="~/js/site.js" asp-append-version="true"></script>
</environment> </environment>
<environment names="Staging,Production"> <environment names="Staging,Production">
<script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-2.2.0.min.js" <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-src="~/lib/jquery/dist/jquery.min.js"
asp-fallback-test="window.jQuery"> asp-fallback-test="window.jQuery">
</script> </script>
<script src="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.6/bootstrap.min.js" <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-src="~/lib/bootstrap/dist/js/bootstrap.min.js"
asp-fallback-test="window.jQuery && window.jQuery.fn && window.jQuery.fn.modal"> asp-fallback-test="window.jQuery && window.jQuery.fn && window.jQuery.fn.modal">
</script> </script>
<script src="~/js/site.min.js" asp-append-version="true"></script> <script src="~/js/site.min.js" asp-append-version="true"></script>
</environment> </environment>
@RenderSection("scripts", required: false) @RenderSection("scripts", required: false)
</body> </body>
</html> </html>

View File

@@ -39,7 +39,7 @@
} }
else 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"> <div class="esh-identity">
<section class="esh-identity-section"> <section class="esh-identity-section">
<div class="esh-identity-item"> <div class="esh-identity-item">
@@ -52,6 +52,7 @@ else
</div> </div>
</section> </section>
<section class="col-lg-1 col-xs-12">@await Component.InvokeAsync("Basket") <section class="col-lg-1 col-xs-12">
</section> @await Component.InvokeAsync("Basket")
</section>
} }

View File

@@ -3,8 +3,12 @@
text-align: center; text-align: center;
} }
.esh-pager-item { .esh-pager-item-left {
margin: 0 5vw; float: left;
}
.esh-pager-item-right {
float: right;
} }
.esh-pager-item--navigable { .esh-pager-item--navigable {