Cart Updates (#26)

* ardalis/cart-updates

Updating how items are added to cart and displayed in cart.

* Cleaning up UI
This commit is contained in:
Steve Smith
2017-08-07 09:49:12 -04:00
committed by GitHub
parent b52048b74d
commit b67f8cc050
16 changed files with 258 additions and 22 deletions

View File

@@ -8,19 +8,20 @@ namespace Microsoft.eShopWeb.ApplicationCore.Entities
public string BuyerId { get; set; } public string BuyerId { get; set; }
public List<BasketItem> Items { get; set; } = new List<BasketItem>(); public List<BasketItem> Items { get; set; } = new List<BasketItem>();
public void AddItem(int productId, decimal unitPrice, int quantity = 1) public void AddItem(CatalogItem item, decimal unitPrice, int quantity = 1)
{ {
if(!Items.Any(i => i.ProductId == productId)) if(!Items.Any(i => i.Item.Id == item.Id))
{ {
Items.Add(new BasketItem() Items.Add(new BasketItem()
{ {
ProductId = productId, Item = item,
//ProductId = productId,
Quantity = quantity, Quantity = quantity,
UnitPrice = unitPrice UnitPrice = unitPrice
}); });
return; return;
} }
var existingItem = Items.FirstOrDefault(i => i.ProductId == productId); var existingItem = Items.FirstOrDefault(i => i.Item.Id == item.Id);
existingItem.Quantity += quantity; existingItem.Quantity += quantity;
} }
} }

View File

@@ -2,8 +2,9 @@
{ {
public class BasketItem : BaseEntity<string> public class BasketItem : BaseEntity<string>
{ {
public int ProductId { get; set; } //public int ProductId { get; set; }
public decimal UnitPrice { get; set; } public decimal UnitPrice { get; set; }
public int Quantity { get; set; } public int Quantity { get; set; }
public CatalogItem Item { get; set; }
} }
} }

View File

@@ -10,6 +10,5 @@
public CatalogType CatalogType { get; set; } public CatalogType CatalogType { get; set; }
public int CatalogBrandId { get; set; } public int CatalogBrandId { get; set; }
public CatalogBrand CatalogBrand { get; set; } public CatalogBrand CatalogBrand { get; set; }
public CatalogItem() { }
} }
} }

View File

@@ -8,6 +8,8 @@ namespace ApplicationCore.Interfaces
Task<Basket> GetBasket(string basketId); Task<Basket> GetBasket(string basketId);
Task<Basket> CreateBasket(); Task<Basket> CreateBasket();
Task<Basket> CreateBasketForUser(string userId); Task<Basket> CreateBasketForUser(string userId);
Task UpdateBasket(Basket basket);
Task AddItemToBasket(Basket basket, int productId, int quantity);
//Task UpdateBasket(Basket basket);
} }
} }

View File

@@ -0,0 +1,8 @@
namespace ApplicationCore.Interfaces
{
public interface IUriComposer
{
string ComposePicUri(string uriTemplate);
}
}

View File

@@ -0,0 +1,20 @@
using ApplicationCore.Interfaces;
using Microsoft.eShopWeb;
namespace ApplicationCore.Services
{
public class UriComposer : IUriComposer
{
private readonly CatalogSettings _catalogSettings;
public UriComposer(CatalogSettings catalogSettings)
{
_catalogSettings = catalogSettings;
}
public string ComposePicUri(string uriTemplate)
{
return uriTemplate.Replace("http://catalogbaseurltobereplaced", _catalogSettings.CatalogBaseUrl);
}
}
}

View File

@@ -3,6 +3,8 @@ using System.Threading.Tasks;
using ApplicationCore.Interfaces; using ApplicationCore.Interfaces;
using Microsoft.eShopWeb.ApplicationCore.Entities; using Microsoft.eShopWeb.ApplicationCore.Entities;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.eShopWeb.ViewModels;
using System.Linq;
namespace Microsoft.eShopWeb.Controllers namespace Microsoft.eShopWeb.Controllers
{ {
@@ -11,12 +13,15 @@ namespace Microsoft.eShopWeb.Controllers
private readonly IBasketService _basketService; private readonly IBasketService _basketService;
//private readonly IIdentityParser<ApplicationUser> _appUserParser; //private readonly IIdentityParser<ApplicationUser> _appUserParser;
private const string _basketSessionKey = "basketId"; private const string _basketSessionKey = "basketId";
private readonly IUriComposer _uriComposer;
public CartController(IBasketService basketService) public CartController(IBasketService basketService,
IUriComposer uriComposer)
// IIdentityParser<ApplicationUser> appUserParser) // IIdentityParser<ApplicationUser> appUserParser)
{ {
_basketService = basketService; _basketService = basketService;
// _appUserParser = appUserParser; _uriComposer = uriComposer;
// _appUserParser = appUserParser;
} }
@@ -26,7 +31,21 @@ namespace Microsoft.eShopWeb.Controllers
//var user = _appUserParser.Parse(HttpContext.User); //var user = _appUserParser.Parse(HttpContext.User);
var basket = await GetBasketFromSessionAsync(); var basket = await GetBasketFromSessionAsync();
return View(basket); var viewModel = new BasketViewModel()
{
BuyerId = basket.BuyerId,
Items = basket.Items.Select(i => new BasketItemViewModel()
{
Id = i.Id,
UnitPrice = i.UnitPrice,
PictureUrl = _uriComposer.ComposePicUri(i.Item.PictureUri),
ProductId = i.Item.Id.ToString(),
ProductName = i.Item.Name,
Quantity = i.Quantity
}).ToList()
};
return View(viewModel);
} }
// GET: /Cart/AddToCart // GET: /Cart/AddToCart
@@ -39,9 +58,7 @@ namespace Microsoft.eShopWeb.Controllers
} }
var basket = await GetBasketFromSessionAsync(); var basket = await GetBasketFromSessionAsync();
basket.AddItem(productDetails.Id, productDetails.Price, 1); await _basketService.AddItemToBasket(basket, productDetails.Id, 1);
await _basketService.UpdateBasket(basket);
return RedirectToAction("Index"); return RedirectToAction("Index");
} }

View File

@@ -3,6 +3,7 @@ using System.Threading.Tasks;
using Microsoft.eShopWeb.ApplicationCore.Entities; using Microsoft.eShopWeb.ApplicationCore.Entities;
using Microsoft.eShopWeb.Infrastructure; using Microsoft.eShopWeb.Infrastructure;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using System;
namespace Web.Services namespace Web.Services
{ {
@@ -18,6 +19,7 @@ namespace Web.Services
{ {
var basket = await _context.Baskets var basket = await _context.Baskets
.Include(b => b.Items) .Include(b => b.Items)
.ThenInclude(i => i.Item)
.FirstOrDefaultAsync(b => b.Id == basketId); .FirstOrDefaultAsync(b => b.Id == basketId);
if (basket == null) if (basket == null)
{ {
@@ -43,11 +45,19 @@ namespace Web.Services
} }
public async Task UpdateBasket(Basket basket) //public async Task UpdateBasket(Basket basket)
//{
// // only need to save changes here
// await _context.SaveChangesAsync();
//}
public async Task AddItemToBasket(Basket basket, int productId, int quantity)
{ {
// only need to save changes here var item = await _context.CatalogItems.FirstOrDefaultAsync(i => i.Id == productId);
basket.AddItem(item, item.Price, quantity);
await _context.SaveChangesAsync(); await _context.SaveChangesAsync();
} }
} }
} }

View File

@@ -16,6 +16,7 @@ using Infrastructure.FileSystem;
using Infrastructure.Logging; using Infrastructure.Logging;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Web.Services; using Web.Services;
using ApplicationCore.Services;
namespace Microsoft.eShopWeb namespace Microsoft.eShopWeb
{ {
@@ -71,7 +72,11 @@ namespace Microsoft.eShopWeb
services.AddScoped<IBasketService, BasketService>(); services.AddScoped<IBasketService, BasketService>();
services.AddScoped<CatalogService>(); services.AddScoped<CatalogService>();
services.Configure<CatalogSettings>(Configuration); services.Configure<CatalogSettings>(Configuration);
services.AddSingleton<IUriComposer>(new UriComposer(Configuration.Get<CatalogSettings>()));
// TODO: Remove
services.AddSingleton<IImageService, LocalFileImageService>(); services.AddSingleton<IImageService, LocalFileImageService>();
services.AddScoped(typeof(IAppLogger<>), typeof(LoggerAdapter<>)); services.AddScoped(typeof(IAppLogger<>), typeof(LoggerAdapter<>));

View File

@@ -0,0 +1,19 @@
using Microsoft.eShopWeb.ApplicationCore.Entities;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Microsoft.eShopWeb.ViewModels
{
public class BasketItemViewModel
{
public string Id { get; set; }
public string ProductId { get; set; }
public string ProductName { get; set; }
public decimal UnitPrice { get; set; }
public decimal OldUnitPrice { get; set; }
public int Quantity { get; set; }
public string PictureUrl { get; set; }
}
}

View File

@@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Microsoft.eShopWeb.ViewModels
{
public class BasketViewModel
{
public List<BasketItemViewModel> Items { get; set; } = new List<BasketItemViewModel>();
public string BuyerId { get; set; }
public decimal Total()
{
return Math.Round(Items.Sum(x => x.UnitPrice * x.Quantity), 2);
}
}
}

View File

@@ -1,7 +1,7 @@
@using Microsoft.eShopWeb.ApplicationCore.Entities; @using Microsoft.eShopWeb.ViewModels
@{ @{
ViewData["Title"] = "Catalog"; ViewData["Title"] = "Cart";
@model Basket @model BasketViewModel
} }
<section class="esh-catalog-hero"> <section class="esh-catalog-hero">
<div class="container"> <div class="container">
@@ -13,11 +13,60 @@
@if (Model.Items.Any()) @if (Model.Items.Any())
{ {
<article class="esh-basket-titles row">
<br />
<section class="esh-basket-title col-xs-3">Product</section>
<section class="esh-basket-title col-xs-3 hidden-lg-down"></section>
<section class="esh-basket-title col-xs-2">Price</section>
<section class="esh-basket-title col-xs-2">Quantity</section>
<section class="esh-basket-title col-xs-2">Cost</section>
</article>
<div class="esh-catalog-items row"> <div class="esh-catalog-items row">
@foreach (var item in Model.Items) @foreach (var item in Model.Items)
{ {
<div class="esh-catalog-item col-md-4"> <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" />
</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">
<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" />
</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 class="row">
</div>
</article>
@*<div class="esh-catalog-item col-md-4">
@item.ProductId @item.ProductId
</div>*@
<div class="container">
<article class="esh-basket-titles esh-basket-titles--clean row">
<section class="esh-basket-title col-xs-10"></section>
<section class="esh-basket-title col-xs-2">Total</section>
</article>
<article class="esh-basket-items row">
<section class="esh-basket-item col-xs-10"></section>
<section class="esh-basket-item esh-basket-item--mark col-xs-2">$ @Model.Total()</section>
</article>
<article class="esh-basket-items row">
<section class="esh-basket-item col-xs-7"></section>
<section class="esh-basket-item col-xs-2">
<button class="btn esh-basket-checkout" name="name" value="" type="submit">[ Update ]</button>
</section>
<section class="esh-basket-item col-xs-3">
<input type="submit"
class="btn esh-basket-checkout"
value="[ Checkout ]" name="action" />
</section>
</article>
</div> </div>
} }
</div> </div>

View File

@@ -3,7 +3,7 @@
<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.eShopOnContainers.WebMVC</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" />

View File

@@ -10,6 +10,13 @@
<DockerComposeProjectPath>..\docker-compose.dcproj</DockerComposeProjectPath> <DockerComposeProjectPath>..\docker-compose.dcproj</DockerComposeProjectPath>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<Compile Remove="Pics\**" />
<Content Remove="Pics\**" />
<EmbeddedResource Remove="Pics\**" />
<None Remove="Pics\**" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Dapper" Version="1.50.2" /> <PackageReference Include="Dapper" Version="1.50.2" />
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.0.1" /> <PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.0.1" />
@@ -27,7 +34,6 @@
<PackageReference Include="Microsoft.VisualStudio.Web.BrowserLink" Version="1.1.2" /> <PackageReference Include="Microsoft.VisualStudio.Web.BrowserLink" Version="1.1.2" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Folder Include="Pics\" />
<Folder Include="Views\Catalog\" /> <Folder Include="Views\Catalog\" />
<Folder Include="Views\Account\" /> <Folder Include="Views\Account\" />
<Folder Include="wwwroot\css\catalog\" /> <Folder Include="wwwroot\css\catalog\" />

View File

@@ -145,3 +145,84 @@
.esh-catalog-price::before { .esh-catalog-price::before {
content: '$'; content: '$';
} }
.esh-basket {
min-height: 80vh;
}
.esh-basket-titles {
padding-bottom: 1rem;
padding-top: 2rem;
}
.esh-basket-titles--clean {
padding-bottom: 0;
padding-top: 0;
}
.esh-basket-title {
text-transform: uppercase;
}
.esh-basket-items--border {
border-bottom: 1px solid #EEEEEE;
padding: .5rem 0;
}
.esh-basket-items--border:last-of-type {
border-color: transparent;
}
.esh-basket-items-margin-left1 {
margin-left: 1px;
}
.esh-basket-item {
font-size: 1rem;
font-weight: 300;
}
.esh-basket-item--middle {
line-height: 8rem;
}
@media screen and (max-width: 1024px) {
.esh-basket-item--middle {
line-height: 1rem;
}
}
.esh-basket-item--mark {
color: #00A69C;
}
.esh-basket-image {
height: 8rem;
}
.esh-basket-input {
line-height: 1rem;
width: 100%;
}
.esh-basket-checkout {
background-color: #83D01B;
border: 0;
border-radius: 0;
color: #FFFFFF;
display: inline-block;
font-size: 1rem;
font-weight: 400;
margin-top: 1rem;
padding: 1rem 1.5rem;
text-align: center;
text-transform: uppercase;
transition: all 0.35s;
}
.esh-basket-checkout:hover {
background-color: #4a760f;
transition: all 0.35s;
}