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,8 +23,9 @@
<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++)
{ {
var item = Model.Items[i];
<article class="esh-basket-items row"> <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">
@@ -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,10 +1,11 @@
@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">
<a class="esh-pager-item-left esh-pager-item--navigable @Model.Previous"
id="Previous" id="Previous"
asp-controller="Catalog" asp-controller="Catalog"
asp-action="Index" asp-action="Index"
@@ -12,12 +13,16 @@
aria-label="Previous"> aria-label="Previous">
Previous Previous
</a> </a>
</div>
<div class="col-md-8 col-xs-12">
<span class="esh-pager-item"> <span class="esh-pager-item">
Showing @Model.ItemsPerPage of @Model.TotalItems products - Page @(Model.ActualPage + 1) - @Model.TotalPages Showing @Model.ItemsPerPage of @Model.TotalItems products - Page @(Model.ActualPage + 1) - @Model.TotalPages
</span> </span>
</div>
<a class="esh-pager-item esh-pager-item--navigable @Model.Next" <div class="col-md-2 col-xs-12">
<a class="esh-pager-item-right esh-pager-item--navigable @Model.Next"
id="Next" id="Next"
asp-controller="Catalog" asp-controller="Catalog"
asp-action="Index" asp-action="Index"
@@ -25,6 +30,7 @@
aria-label="Next"> aria-label="Next">
Next Next
</a> </a>
</div>
</nav> </nav>
</article> </article>
</div> </div>

View File

@@ -8,18 +8,6 @@
<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>
<environment names="Staging,Production"> <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" <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha.5/css/bootstrap.min.css"
@@ -27,8 +15,11 @@
asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute" /> 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" /> <link rel="stylesheet" href="~/css/app.min.css" asp-append-version="true" />
</environment> </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">
@@ -36,13 +27,12 @@
<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-xs-12"><a asp-controller="Basket" asp-action="Index">Basket</a></section> <section class="col-lg-1 col-md-3 col-xs-6"><a asp-controller="Basket" asp-action="Index">Basket</a></section>
</article> </article>
</div> </div>

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">
@await Component.InvokeAsync("Basket")
</section> </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 {