Browse Source
* Ardalis/upgrade1 (#44) * Upgrading to netcore 2.0 Updating repository to support async options and refactoring to use it. * Starting work on tracking customer orders feature. * Cleaning up some bugs Working on basket view component implementation * Fixing up styles, especially for basket in header. * Adding Order Features (#47) * Working on order model binding from checkout page - WIP * Small layout tweaks (#43) * Updating quantities implemented. * Fixed basket widget count * Order History (#49) * working on creating and viewing orders. * Working on wiring up listing of orders * List orders page works as expected. Needed to support ThenInclude scenarios. Currently using strings.main
committed by
GitHub
70 changed files with 1752 additions and 510 deletions
@ -0,0 +1,26 @@ |
|||
using ApplicationCore.Interfaces; |
|||
using Microsoft.eShopWeb.ApplicationCore.Entities; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
|
|||
namespace ApplicationCore.Entities.BuyerAggregate |
|||
{ |
|||
public class Buyer : BaseEntity, IAggregateRoot |
|||
{ |
|||
public string IdentityGuid { get; private set; } |
|||
|
|||
private List<PaymentMethod> _paymentMethods = new List<PaymentMethod>(); |
|||
|
|||
public IEnumerable<PaymentMethod> PaymentMethods => _paymentMethods.AsReadOnly(); |
|||
|
|||
protected Buyer() |
|||
{ |
|||
} |
|||
|
|||
public Buyer(string identity) : this() |
|||
{ |
|||
IdentityGuid = !string.IsNullOrWhiteSpace(identity) ? identity : throw new ArgumentNullException(nameof(identity)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
using Microsoft.eShopWeb.ApplicationCore.Entities; |
|||
|
|||
namespace ApplicationCore.Entities.BuyerAggregate |
|||
{ |
|||
public class PaymentMethod : BaseEntity |
|||
{ |
|||
public string Alias { get; set; } |
|||
public string CardId { get; set; } // actual card data must be stored in a PCI compliant system, like Stripe
|
|||
public string Last4 { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace ApplicationCore.Entities.OrderAggregate |
|||
{ |
|||
public class Address // ValueObject
|
|||
{ |
|||
public String Street { get; private set; } |
|||
|
|||
public String City { get; private set; } |
|||
|
|||
public String State { get; private set; } |
|||
|
|||
public String Country { get; private set; } |
|||
|
|||
public String ZipCode { get; private set; } |
|||
|
|||
private Address() { } |
|||
|
|||
public Address(string street, string city, string state, string country, string zipcode) |
|||
{ |
|||
Street = street; |
|||
City = city; |
|||
State = state; |
|||
Country = country; |
|||
ZipCode = zipcode; |
|||
} |
|||
|
|||
//protected override IEnumerable<object> GetAtomicValues()
|
|||
//{
|
|||
// yield return Street;
|
|||
// yield return City;
|
|||
// yield return State;
|
|||
// yield return Country;
|
|||
// yield return ZipCode;
|
|||
//}
|
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
namespace ApplicationCore.Entities.OrderAggregate |
|||
{ |
|||
/// <summary>
|
|||
/// Represents the item that was ordered. If catalog item details change, details of
|
|||
/// the item that was part of a completed order should not change.
|
|||
/// </summary>
|
|||
public class CatalogItemOrdered // ValueObject
|
|||
{ |
|||
public CatalogItemOrdered(int catalogItemId, string productName, string pictureUri) |
|||
{ |
|||
CatalogItemId = catalogItemId; |
|||
ProductName = productName; |
|||
PictureUri = pictureUri; |
|||
} |
|||
private CatalogItemOrdered() |
|||
{ |
|||
// required by EF
|
|||
} |
|||
public int CatalogItemId { get; private set; } |
|||
public string ProductName { get; private set; } |
|||
public string PictureUri { get; private set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,48 @@ |
|||
using ApplicationCore.Interfaces; |
|||
using Microsoft.eShopWeb.ApplicationCore.Entities; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace ApplicationCore.Entities.OrderAggregate |
|||
{ |
|||
public class Order : BaseEntity, IAggregateRoot |
|||
{ |
|||
private Order() |
|||
{ |
|||
} |
|||
|
|||
public Order(string buyerId, Address shipToAddress, List<OrderItem> items) |
|||
{ |
|||
ShipToAddress = shipToAddress; |
|||
_orderItems = items; |
|||
BuyerId = buyerId; |
|||
} |
|||
public string BuyerId { get; private set; } |
|||
|
|||
public DateTimeOffset OrderDate { get; private set; } = DateTimeOffset.Now; |
|||
public Address ShipToAddress { get; private set; } |
|||
|
|||
// DDD Patterns comment
|
|||
// Using a private collection field, better for DDD Aggregate's encapsulation
|
|||
// so OrderItems cannot be added from "outside the AggregateRoot" directly to the collection,
|
|||
// but only through the method Order.AddOrderItem() which includes behavior.
|
|||
private readonly List<OrderItem> _orderItems = new List<OrderItem>(); |
|||
|
|||
public IReadOnlyCollection<OrderItem> OrderItems => _orderItems; |
|||
// Using List<>.AsReadOnly()
|
|||
// This will create a read only wrapper around the private list so is protected against "external updates".
|
|||
// It's much cheaper than .ToList() because it will not have to copy all items in a new collection. (Just one heap alloc for the wrapper instance)
|
|||
//https://msdn.microsoft.com/en-us/library/e78dcd75(v=vs.110).aspx
|
|||
|
|||
public decimal Total() |
|||
{ |
|||
var total = 0m; |
|||
foreach (var item in _orderItems) |
|||
{ |
|||
total += item.UnitPrice * item.Units; |
|||
} |
|||
return total; |
|||
} |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
using Microsoft.eShopWeb.ApplicationCore.Entities; |
|||
|
|||
namespace ApplicationCore.Entities.OrderAggregate |
|||
{ |
|||
|
|||
public class OrderItem : BaseEntity |
|||
{ |
|||
public CatalogItemOrdered ItemOrdered { get; private set; } |
|||
public decimal UnitPrice { get; private set; } |
|||
public int Units { get; private set; } |
|||
|
|||
protected OrderItem() |
|||
{ |
|||
} |
|||
public OrderItem(CatalogItemOrdered itemOrdered, decimal unitPrice, int units) |
|||
{ |
|||
ItemOrdered = itemOrdered; |
|||
UnitPrice = unitPrice; |
|||
Units = units; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,5 @@ |
|||
namespace ApplicationCore.Interfaces |
|||
{ |
|||
public interface IAggregateRoot |
|||
{ } |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
using Microsoft.eShopWeb.ApplicationCore.Entities; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace ApplicationCore.Interfaces |
|||
{ |
|||
public interface IAsyncRepository<T> where T : BaseEntity |
|||
{ |
|||
Task<T> GetByIdAsync(int id); |
|||
Task<List<T>> ListAllAsync(); |
|||
Task<List<T>> ListAsync(ISpecification<T> spec); |
|||
Task<T> AddAsync(T entity); |
|||
Task UpdateAsync(T entity); |
|||
Task DeleteAsync(T entity); |
|||
} |
|||
} |
|||
@ -1,7 +0,0 @@ |
|||
namespace ApplicationCore.Interfaces |
|||
{ |
|||
public interface IImageService |
|||
{ |
|||
byte[] GetImageBytesById(int id); |
|||
} |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
using ApplicationCore.Entities.OrderAggregate; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace ApplicationCore.Interfaces |
|||
{ |
|||
|
|||
public interface IOrderRepository : IRepository<Order>, IAsyncRepository<Order> |
|||
{ |
|||
Order GetByIdWithItems(int id); |
|||
Task<Order> GetByIdWithItemsAsync(int id); |
|||
} |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
using ApplicationCore.Entities.OrderAggregate; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace ApplicationCore.Interfaces |
|||
{ |
|||
public interface IOrderService |
|||
{ |
|||
Task CreateOrderAsync(int basketId, Address shippingAddress); |
|||
} |
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
using ApplicationCore.Interfaces; |
|||
using System; |
|||
using System.Linq.Expressions; |
|||
using System.Collections.Generic; |
|||
using ApplicationCore.Entities.OrderAggregate; |
|||
|
|||
namespace ApplicationCore.Specifications |
|||
{ |
|||
public class CustomerOrdersWithItemsSpecification : ISpecification<Order> |
|||
{ |
|||
private readonly string _buyerId; |
|||
|
|||
public CustomerOrdersWithItemsSpecification(string buyerId) |
|||
{ |
|||
_buyerId = buyerId; |
|||
AddInclude(o => o.OrderItems); |
|||
AddInclude("OrderItems.ItemOrdered"); |
|||
} |
|||
|
|||
public Expression<Func<Order, bool>> Criteria => o => o.BuyerId == _buyerId; |
|||
|
|||
public List<Expression<Func<Order, object>>> Includes { get; } = new List<Expression<Func<Order, object>>>(); |
|||
public List<string> IncludeStrings { get; } = new List<string>(); |
|||
|
|||
public void AddInclude(Expression<Func<Order, object>> includeExpression) |
|||
{ |
|||
Includes.Add(includeExpression); |
|||
} |
|||
|
|||
public void AddInclude(string includeString) |
|||
{ |
|||
IncludeStrings.Add(includeString); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
using ApplicationCore.Entities.OrderAggregate; |
|||
using ApplicationCore.Interfaces; |
|||
using Microsoft.EntityFrameworkCore; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Infrastructure.Data |
|||
{ |
|||
public class OrderRepository : EfRepository<Order>, IOrderRepository |
|||
{ |
|||
public OrderRepository(CatalogContext dbContext) : base(dbContext) |
|||
{ |
|||
} |
|||
|
|||
public Order GetByIdWithItems(int id) |
|||
{ |
|||
return _dbContext.Orders |
|||
.Include(o => o.OrderItems) |
|||
.Include("OrderItems.ItemOrdered") |
|||
.FirstOrDefault(); |
|||
} |
|||
|
|||
public Task<Order> GetByIdWithItemsAsync(int id) |
|||
{ |
|||
return _dbContext.Orders |
|||
.Include(o => o.OrderItems) |
|||
.Include("OrderItems.ItemOrdered") |
|||
.FirstOrDefaultAsync(); |
|||
} |
|||
} |
|||
} |
|||
@ -1,30 +0,0 @@ |
|||
using ApplicationCore.Exceptions; |
|||
using ApplicationCore.Interfaces; |
|||
using Microsoft.AspNetCore.Hosting; |
|||
using System.IO; |
|||
|
|||
namespace Infrastructure.FileSystem |
|||
{ |
|||
public class LocalFileImageService : IImageService |
|||
{ |
|||
private readonly IHostingEnvironment _env; |
|||
|
|||
public LocalFileImageService(IHostingEnvironment env) |
|||
{ |
|||
_env = env; |
|||
} |
|||
public byte[] GetImageBytesById(int id) |
|||
{ |
|||
try |
|||
{ |
|||
var contentRoot = _env.ContentRootPath + "//Pics"; |
|||
var path = Path.Combine(contentRoot, id + ".png"); |
|||
return File.ReadAllBytes(path); |
|||
} |
|||
catch (FileNotFoundException ex) |
|||
{ |
|||
throw new CatalogImageMissingException(ex); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,30 +1,26 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netstandard1.4</TargetFramework> |
|||
<TargetFramework>netcoreapp2.0</TargetFramework> |
|||
</PropertyGroup> |
|||
|
|||
<PropertyGroup> |
|||
<RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion> |
|||
</PropertyGroup> |
|||
<ItemGroup> |
|||
<PackageReference Include="Microsoft.AspNetCore" Version="1.1.2" /> |
|||
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="1.1.2" /> |
|||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="1.1.2" /> |
|||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="1.1.2" PrivateAssets="All" /> |
|||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="1.1.2" /> |
|||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer.Design" Version="1.1.2" PrivateAssets="All" /> |
|||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="1.1.1" PrivateAssets="All" /> |
|||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="1.1.2" /> |
|||
<PackageReference Include="StructureMap.Microsoft.DependencyInjection" Version="1.3.0" /> |
|||
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" /> |
|||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.0.0" PrivateAssets="All" /> |
|||
</ItemGroup> |
|||
<ItemGroup> |
|||
<ProjectReference Include="..\ApplicationCore\ApplicationCore.csproj" /> |
|||
</ItemGroup> |
|||
<ItemGroup> |
|||
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools" Version="1.1.1" /> |
|||
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.0.1" /> |
|||
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Design" Version="1.1.2" /> |
|||
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.0.0" /> |
|||
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.0" /> |
|||
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.0.0" /> |
|||
</ItemGroup> |
|||
<ItemGroup> |
|||
<Folder Include="Data\Migrations\" /> |
|||
<Folder Include="Services\" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,40 @@ |
|||
using ApplicationCore.Interfaces; |
|||
using ApplicationCore.Entities.OrderAggregate; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.eShopWeb.ApplicationCore.Entities; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Infrastructure.Services |
|||
{ |
|||
public class OrderService : IOrderService |
|||
{ |
|||
private readonly IAsyncRepository<Order> _orderRepository; |
|||
private readonly IAsyncRepository<Basket> _basketRepository; |
|||
private readonly IAsyncRepository<CatalogItem> _itemRepository; |
|||
|
|||
public OrderService(IAsyncRepository<Basket> basketRepository, |
|||
IAsyncRepository<CatalogItem> itemRepository, |
|||
IAsyncRepository<Order> orderRepository) |
|||
{ |
|||
_orderRepository = orderRepository; |
|||
_basketRepository = basketRepository; |
|||
_itemRepository = itemRepository; |
|||
} |
|||
|
|||
public async Task CreateOrderAsync(int basketId, Address shippingAddress) |
|||
{ |
|||
var basket = await _basketRepository.GetByIdAsync(basketId); |
|||
var items = new List<OrderItem>(); |
|||
foreach (var item in basket.Items) |
|||
{ |
|||
var catalogItem = await _itemRepository.GetByIdAsync(item.CatalogItemId); |
|||
var itemOrdered = new CatalogItemOrdered(catalogItem.Id, catalogItem.Name, catalogItem.PictureUri); |
|||
var orderItem = new OrderItem(itemOrdered, item.UnitPrice, item.Quantity); |
|||
items.Add(orderItem); |
|||
} |
|||
var order = new Order(basket.BuyerId, shippingAddress, items); |
|||
|
|||
await _orderRepository.AddAsync(order); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,96 @@ |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Authorization; |
|||
using Microsoft.eShopWeb.ViewModels; |
|||
using System; |
|||
using ApplicationCore.Entities.OrderAggregate; |
|||
using ApplicationCore.Interfaces; |
|||
using System.Linq; |
|||
using ApplicationCore.Specifications; |
|||
|
|||
namespace Microsoft.eShopWeb.Controllers |
|||
{ |
|||
[Authorize] |
|||
[Route("[controller]/[action]")]
|
|||
public class OrderController : Controller |
|||
{ |
|||
private readonly IOrderRepository _orderRepository; |
|||
|
|||
public OrderController(IOrderRepository orderRepository) { |
|||
_orderRepository = orderRepository; |
|||
} |
|||
|
|||
public async Task<IActionResult> Index() |
|||
{ |
|||
var orders = await _orderRepository.ListAsync(new CustomerOrdersWithItemsSpecification(User.Identity.Name)); |
|||
|
|||
var viewModel = orders |
|||
.Select(o => new OrderViewModel() |
|||
{ |
|||
OrderDate = o.OrderDate, |
|||
OrderItems = o.OrderItems?.Select(oi => new OrderItemViewModel() |
|||
{ |
|||
Discount = 0, |
|||
PictureUrl = oi.ItemOrdered.PictureUri, |
|||
ProductId = oi.ItemOrdered.CatalogItemId, |
|||
ProductName = oi.ItemOrdered.ProductName, |
|||
UnitPrice = oi.UnitPrice, |
|||
Units = oi.Units |
|||
}).ToList(), |
|||
OrderNumber = o.Id, |
|||
ShippingAddress = o.ShipToAddress, |
|||
Status = "Pending", |
|||
Total = o.Total() |
|||
|
|||
}); |
|||
return View(viewModel); |
|||
} |
|||
|
|||
[HttpGet("{orderId}")] |
|||
public async Task<IActionResult> Detail(int orderId) |
|||
{ |
|||
var order = await _orderRepository.GetByIdWithItemsAsync(orderId); |
|||
var viewModel = new OrderViewModel() |
|||
{ |
|||
OrderDate = order.OrderDate, |
|||
OrderItems = order.OrderItems.Select(oi => new OrderItemViewModel() |
|||
{ |
|||
Discount = 0, |
|||
PictureUrl = oi.ItemOrdered.PictureUri, |
|||
ProductId = oi.ItemOrdered.CatalogItemId, |
|||
ProductName = oi.ItemOrdered.ProductName, |
|||
UnitPrice = oi.UnitPrice, |
|||
Units = oi.Units |
|||
}).ToList(), |
|||
OrderNumber = order.Id, |
|||
ShippingAddress = order.ShipToAddress, |
|||
Status = "Pending", |
|||
Total = order.Total() |
|||
}; |
|||
return View(viewModel); |
|||
} |
|||
|
|||
private OrderViewModel GetOrder() |
|||
{ |
|||
var order = new OrderViewModel() |
|||
{ |
|||
OrderDate = DateTimeOffset.Now.AddDays(-1), |
|||
OrderNumber = 12354, |
|||
Status = "Submitted", |
|||
Total = 123.45m, |
|||
ShippingAddress = new Address("123 Main St.", "Kent", "OH", "United States", "44240") |
|||
}; |
|||
|
|||
order.OrderItems.Add(new OrderItemViewModel() |
|||
{ |
|||
ProductId = 1, |
|||
PictureUrl = "", |
|||
ProductName = "Something", |
|||
UnitPrice = 5.05m, |
|||
Units = 2 |
|||
}); |
|||
|
|||
return order; |
|||
} |
|||
} |
|||
} |
|||
@ -1,28 +1,20 @@ |
|||
using System.IO; |
|||
using Microsoft.AspNetCore.Hosting; |
|||
using Microsoft.Extensions.Logging; |
|||
using Microsoft.AspNetCore.Hosting; |
|||
using Microsoft.AspNetCore; |
|||
|
|||
namespace Microsoft.eShopWeb |
|||
{ |
|||
public class Program |
|||
{ |
|||
|
|||
public static void Main(string[] args) |
|||
{ |
|||
var host = new WebHostBuilder() |
|||
.UseKestrel() |
|||
BuildWebHost(args).Run(); |
|||
} |
|||
|
|||
public static IWebHost BuildWebHost(string[] args) => |
|||
WebHost.CreateDefaultBuilder(args) |
|||
.UseUrls("http://0.0.0.0:5106") |
|||
.UseContentRoot(Directory.GetCurrentDirectory()) |
|||
.ConfigureLogging(factory => |
|||
{ |
|||
factory.AddConsole(LogLevel.Warning); |
|||
factory.AddDebug(); |
|||
}) |
|||
.UseIISIntegration() |
|||
.UseStartup<Startup>() |
|||
.UseApplicationInsights() |
|||
.Build(); |
|||
|
|||
host.Run(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +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 _basketService; |
|||
private readonly SignInManager<ApplicationUser> _signInManager; |
|||
|
|||
public Basket(IBasketService basketService, |
|||
SignInManager<ApplicationUser> signInManager) |
|||
{ |
|||
_basketService = basketService; |
|||
_signInManager = signInManager; |
|||
} |
|||
|
|||
public async Task<IViewComponentResult> InvokeAsync(string userName) |
|||
{ |
|||
var vm = new BasketComponentViewModel(); |
|||
vm.ItemsCount = (await GetBasketViewModelAsync()).Items.Sum(i => i.Quantity); |
|||
return View(vm); |
|||
} |
|||
|
|||
private async Task<BasketViewModel> GetBasketViewModelAsync() |
|||
{ |
|||
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; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
namespace Microsoft.eShopWeb.ViewModels |
|||
{ |
|||
public class BasketComponentViewModel |
|||
{ |
|||
public int ItemsCount { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.ComponentModel.DataAnnotations; |
|||
|
|||
namespace Microsoft.eShopWeb.ViewModels |
|||
{ |
|||
|
|||
public class OrderItemViewModel |
|||
{ |
|||
public int ProductId { get; set; } |
|||
|
|||
public string ProductName { get; set; } |
|||
|
|||
public decimal UnitPrice { get; set; } |
|||
|
|||
public decimal Discount { get; set; } |
|||
|
|||
public int Units { get; set; } |
|||
|
|||
public string PictureUrl { get; set; } |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
using ApplicationCore.Entities.OrderAggregate; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Microsoft.eShopWeb.ViewModels |
|||
{ |
|||
|
|||
public class OrderViewModel |
|||
{ |
|||
public int OrderNumber { get; set; } |
|||
public DateTimeOffset OrderDate { get; set; } |
|||
public decimal Total { get; set; } |
|||
public string Status { get; set; } |
|||
|
|||
public Address ShippingAddress { get; set; } |
|||
|
|||
public List<OrderItemViewModel> OrderItems { get; set; } = new List<OrderItemViewModel>(); |
|||
|
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,88 @@ |
|||
@using Microsoft.eShopWeb.ViewModels |
|||
@model OrderViewModel |
|||
@{ |
|||
ViewData["Title"] = "My Order History"; |
|||
} |
|||
@{ |
|||
ViewData["Title"] = "Order Detail"; |
|||
} |
|||
|
|||
<div class="esh-orders_detail"> |
|||
<div class="container"> |
|||
<section class="esh-orders_detail-section"> |
|||
<article class="esh-orders_detail-titles row"> |
|||
<section class="esh-orders_detail-title col-xs-3">Order number</section> |
|||
<section class="esh-orders_detail-title col-xs-3">Date</section> |
|||
<section class="esh-orders_detail-title col-xs-3">Total</section> |
|||
<section class="esh-orders_detail-title col-xs-3">Status</section> |
|||
</article> |
|||
|
|||
<article class="esh-orders_detail-items row"> |
|||
<section class="esh-orders_detail-item col-xs-3">@Model.OrderNumber</section> |
|||
<section class="esh-orders_detail-item col-xs-3">@Model.OrderDate</section> |
|||
<section class="esh-orders_detail-item col-xs-3">$@Model.Total</section> |
|||
<section class="esh-orders_detail-title col-xs-3">@Model.Status</section> |
|||
</article> |
|||
</section> |
|||
|
|||
@*<section class="esh-orders_detail-section"> |
|||
<article class="esh-orders_detail-titles row"> |
|||
<section class="esh-orders_detail-title col-xs-12">Description</section> |
|||
</article> |
|||
|
|||
<article class="esh-orders_detail-items row"> |
|||
<section class="esh-orders_detail-item col-xs-12">@Model.Description</section> |
|||
</article> |
|||
</section>*@ |
|||
|
|||
<section class="esh-orders_detail-section"> |
|||
<article class="esh-orders_detail-titles row"> |
|||
<section class="esh-orders_detail-title col-xs-12">Shipping Address</section> |
|||
</article> |
|||
|
|||
<article class="esh-orders_detail-items row"> |
|||
<section class="esh-orders_detail-item col-xs-12">@Model.ShippingAddress.Street</section> |
|||
</article> |
|||
|
|||
<article class="esh-orders_detail-items row"> |
|||
<section class="esh-orders_detail-item col-xs-12">@Model.ShippingAddress.City</section> |
|||
</article> |
|||
|
|||
<article class="esh-orders_detail-items row"> |
|||
<section class="esh-orders_detail-item col-xs-12">@Model.ShippingAddress.Country</section> |
|||
</article> |
|||
</section> |
|||
|
|||
<section class="esh-orders_detail-section"> |
|||
<article class="esh-orders_detail-titles row"> |
|||
<section class="esh-orders_detail-title col-xs-12">ORDER DETAILS</section> |
|||
</article> |
|||
|
|||
@for (int i = 0; i < Model.OrderItems.Count; i++) |
|||
{ |
|||
var item = Model.OrderItems[i]; |
|||
<article class="esh-orders_detail-items esh-orders_detail-items--border row"> |
|||
<section class="esh-orders_detail-item col-md-4 hidden-md-down"> |
|||
<img class="esh-orders_detail-image" src="@item.PictureUrl"> |
|||
</section> |
|||
<section class="esh-orders_detail-item esh-orders_detail-item--middle col-xs-4">@item.ProductName</section> |
|||
<section class="esh-orders_detail-item esh-orders_detail-item--middle col-xs-1">$ @item.UnitPrice.ToString("N2")</section> |
|||
<section class="esh-orders_detail-item esh-orders_detail-item--middle col-xs-1">@item.Units</section> |
|||
<section class="esh-orders_detail-item esh-orders_detail-item--middle col-xs-2">$ @Math.Round(item.Units * item.UnitPrice, 2).ToString("N2")</section> |
|||
</article> |
|||
} |
|||
</section> |
|||
|
|||
<section class="esh-orders_detail-section esh-orders_detail-section--right"> |
|||
<article class="esh-orders_detail-titles esh-basket-titles--clean row"> |
|||
<section class="esh-orders_detail-title col-xs-9"></section> |
|||
<section class="esh-orders_detail-title col-xs-2">TOTAL</section> |
|||
</article> |
|||
|
|||
<article class="esh-orders_detail-items row"> |
|||
<section class="esh-orders_detail-item col-xs-9"></section> |
|||
<section class="esh-orders_detail-item esh-orders_detail-item--mark col-xs-2">$ @Model.Total</section> |
|||
</article> |
|||
</section> |
|||
</div> |
|||
</div> |
|||
@ -0,0 +1,39 @@ |
|||
@using Microsoft.eShopWeb.ViewModels |
|||
@model IEnumerable<OrderViewModel> |
|||
@{ |
|||
ViewData["Title"] = "My Order History"; |
|||
} |
|||
|
|||
<div class="esh-orders"> |
|||
<div class="container"> |
|||
<h1>@ViewData["Title"]</h1> |
|||
<article class="esh-orders-titles row"> |
|||
<section class="esh-orders-title col-xs-2">Order number</section> |
|||
<section class="esh-orders-title col-xs-4">Date</section> |
|||
<section class="esh-orders-title col-xs-2">Total</section> |
|||
<section class="esh-orders-title col-xs-2">Status</section> |
|||
<section class="esh-orders-title col-xs-2"></section> |
|||
</article> |
|||
@if (Model != null && Model.Any()) |
|||
{ |
|||
@foreach (var item in Model) |
|||
{ |
|||
<article class="esh-orders-items row"> |
|||
<section class="esh-orders-item col-xs-2">@Html.DisplayFor(modelItem => item.OrderNumber)</section> |
|||
<section class="esh-orders-item col-xs-4">@Html.DisplayFor(modelItem => item.OrderDate)</section> |
|||
<section class="esh-orders-item col-xs-2">$ @Html.DisplayFor(modelItem => item.Total)</section> |
|||
<section class="esh-orders-item col-xs-2">@Html.DisplayFor(modelItem => item.Status)</section> |
|||
<section class="esh-orders-item col-xs-1"> |
|||
<a class="esh-orders-link" asp-controller="Order" asp-action="Detail" asp-route-orderId="@item.OrderNumber">Detail</a> |
|||
</section> |
|||
<section class="esh-orders-item col-xs-1"> |
|||
@if (item.Status.ToLower() == "submitted") |
|||
{ |
|||
<a class="esh-orders-link" asp-controller="Order" asp-action="cancel" asp-route-orderId="@item.OrderNumber">Cancel</a> |
|||
} |
|||
</section> |
|||
</article> |
|||
} |
|||
} |
|||
</div> |
|||
</div> |
|||
@ -0,0 +1,17 @@ |
|||
@model BasketComponentViewModel |
|||
|
|||
@{ |
|||
ViewData["Title"] = "My Basket"; |
|||
} |
|||
|
|||
<a class="esh-basketstatus " |
|||
asp-area="" |
|||
asp-controller="Basket" |
|||
asp-action="Index"> |
|||
<div class="esh-basketstatus-image"> |
|||
<img src="~/images/cart.png" /> |
|||
</div> |
|||
<div class="esh-basketstatus-badge"> |
|||
@Model.ItemsCount |
|||
</div> |
|||
</a> |
|||
@ -0,0 +1,42 @@ |
|||
[ |
|||
{ |
|||
"outputFile": "wwwroot/css/orders/orders.component.css", |
|||
"inputFile": "wwwroot/css/orders/orders.component.scss" |
|||
}, |
|||
//{ |
|||
// "outputFile": "wwwroot/css/orders/orders-new/orders-new.component.css", |
|||
// "inputFile": "wwwroot/css/orders/orders-new/orders-new.component.scss" |
|||
//}, |
|||
//{ |
|||
// "outputFile": "wwwroot/css/orders/orders-detail/orders-detail.component.css", |
|||
// "inputFile": "wwwroot/css/orders/orders-detail/orders-detail.component.scss" |
|||
//}, |
|||
{ |
|||
"outputFile": "wwwroot/css/catalog/catalog.component.css", |
|||
"inputFile": "wwwroot/css/catalog/catalog.component.scss" |
|||
}, |
|||
{ |
|||
"outputFile": "wwwroot/css/basket/basket.component.css", |
|||
"inputFile": "wwwroot/css/basket/basket.component.scss" |
|||
}, |
|||
{ |
|||
"outputFile": "wwwroot/css/basket/basket-status/basket-status.component.css", |
|||
"inputFile": "wwwroot/css/basket/basket-status/basket-status.component.scss" |
|||
}, |
|||
//{ |
|||
// "outputFile": "wwwroot/css/shared/components/header/header.css", |
|||
// "inputFile": "wwwroot/css/shared/components/header/header.scss" |
|||
//}, |
|||
//{ |
|||
// "outputFile": "wwwroot/css/shared/components/identity/identity.css", |
|||
// "inputFile": "wwwroot/css/shared/components/identity/identity.scss" |
|||
//}, |
|||
//{ |
|||
// "outputFile": "wwwroot/css/shared/components/pager/pager.css", |
|||
// "inputFile": "wwwroot/css/shared/components/pager/pager.scss" |
|||
//}, |
|||
{ |
|||
"outputFile": "wwwroot/css/app.component.css", |
|||
"inputFile": "wwwroot/css/app.component.scss" |
|||
} |
|||
] |
|||
@ -0,0 +1,65 @@ |
|||
// Colors |
|||
$color-brand: #00A69C; |
|||
$color-brand-dark: darken($color-brand, 10%); |
|||
$color-brand-darker: darken($color-brand, 20%); |
|||
$color-brand-bright: lighten($color-brand, 10%); |
|||
$color-brand-brighter: lighten($color-brand, 20%); |
|||
|
|||
$color-secondary: #83D01B; |
|||
$color-secondary-dark: darken($color-secondary, 5%); |
|||
$color-secondary-darker: darken($color-secondary, 20%); |
|||
$color-secondary-bright: lighten($color-secondary, 10%); |
|||
$color-secondary-brighter: lighten($color-secondary, 20%); |
|||
|
|||
$color-warning: #ff0000; |
|||
$color-warning-dark: darken($color-warning, 5%); |
|||
$color-warning-darker: darken($color-warning, 20%); |
|||
$color-warning-bright: lighten($color-warning, 10%); |
|||
$color-warning-brighter: lighten($color-warning, 20%); |
|||
|
|||
|
|||
$color-background-dark: #333333; |
|||
$color-background-darker: #000000; |
|||
$color-background-bright: #EEEEFF; |
|||
$color-background-brighter: #FFFFFF; |
|||
|
|||
$color-foreground-dark: #333333; |
|||
$color-foreground-darker: #000000; |
|||
$color-foreground-bright: #EEEEEE; |
|||
$color-foreground-brighter: #FFFFFF; |
|||
|
|||
// Animations |
|||
$animation-speed-default: .35s; |
|||
$animation-speed-slow: .5s; |
|||
$animation-speed-fast: .15s; |
|||
|
|||
// Fonts |
|||
$font-weight-light: 200; |
|||
$font-weight-semilight: 300; |
|||
$font-weight-normal: 400; |
|||
$font-weight-semibold: 600; |
|||
$font-weight-bold: 700; |
|||
|
|||
$font-size-xs: .65rem; // 10.4px |
|||
$font-size-s: .85rem; // 13.6px |
|||
$font-size-m: 1rem; // 16px |
|||
$font-size-l: 1.25rem; // 20px |
|||
$font-size-xl: 1.5rem; // 24px |
|||
|
|||
// Medias |
|||
$media-screen-xxs: 360px; |
|||
$media-screen-xs: 640px; |
|||
$media-screen-s: 768px; |
|||
$media-screen-m: 1024px; |
|||
$media-screen-l: 1280px; |
|||
$media-screen-xl: 1440px; |
|||
$media-screen-xxl: 1680px; |
|||
$media-screen-xxxl: 1920px; |
|||
|
|||
// Borders |
|||
$border-light: 1px; |
|||
|
|||
// Images |
|||
$image_path: '../../images/'; |
|||
$image-main_banner: '#{$image_path}main_banner.png'; |
|||
$image-arrow_down: '#{$image_path}arrow-down.png'; |
|||
@ -0,0 +1,11 @@ |
|||
.esh-app-footer { |
|||
background-color: #000000; |
|||
border-top: 1px solid #EEEEEE; |
|||
margin-top: 2.5rem; |
|||
padding-bottom: 2.5rem; |
|||
padding-top: 2.5rem; |
|||
width: 100%; } |
|||
.esh-app-footer-brand { |
|||
height: 50px; |
|||
width: 230px; } |
|||
|
|||
@ -0,0 +1 @@ |
|||
.esh-app-footer{background-color:#000;border-top:1px solid #eee;margin-top:2.5rem;padding-bottom:2.5rem;padding-top:2.5rem;width:100%}.esh-app-footer-brand{height:50px;width:230px} |
|||
@ -0,0 +1,23 @@ |
|||
@import './variables'; |
|||
|
|||
.esh-app { |
|||
&-footer { |
|||
$margin: 2.5rem; |
|||
$padding: 2.5rem; |
|||
|
|||
background-color: $color-background-darker; |
|||
border-top: $border-light solid $color-foreground-bright; |
|||
margin-top: $margin; |
|||
padding-bottom: $padding; |
|||
padding-top: $padding; |
|||
width: 100%; |
|||
|
|||
$height: 50px; |
|||
|
|||
&-brand { |
|||
height: $height; |
|||
width: 230px; |
|||
} |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,43 @@ |
|||
.esh-basketstatus { |
|||
cursor: pointer; |
|||
display: inline-block; |
|||
float: right; |
|||
position: relative; |
|||
transition: all 0.35s; } |
|||
.esh-basketstatus.is-disabled { |
|||
opacity: .5; |
|||
pointer-events: none; } |
|||
.esh-basketstatus-image { |
|||
height: 36px; |
|||
margin-top: .5rem; } |
|||
.esh-basketstatus-badge { |
|||
background-color: #83D01B; |
|||
border-radius: 50%; |
|||
color: #FFFFFF; |
|||
display: block; |
|||
height: 1.5rem; |
|||
left: 50%; |
|||
position: absolute; |
|||
text-align: center; |
|||
top: 0; |
|||
transform: translateX(-38%); |
|||
transition: all 0.35s; |
|||
width: 1.5rem; } |
|||
.esh-basketstatus-badge-inoperative { |
|||
background-color: #ff0000; |
|||
border-radius: 50%; |
|||
color: #FFFFFF; |
|||
display: block; |
|||
height: 1.5rem; |
|||
left: 50%; |
|||
position: absolute; |
|||
text-align: center; |
|||
top: 0; |
|||
transform: translateX(-38%); |
|||
transition: all 0.35s; |
|||
width: 1.5rem; } |
|||
.esh-basketstatus:hover .esh-basketstatus-badge { |
|||
background-color: transparent; |
|||
color: #75b918; |
|||
transition: all 0.35s; } |
|||
|
|||
@ -0,0 +1 @@ |
|||
.esh-basketstatus{cursor:pointer;display:inline-block;float:right;position:relative;transition:all .35s}.esh-basketstatus.is-disabled{opacity:.5;pointer-events:none}.esh-basketstatus-image{height:36px;margin-top:.5rem}.esh-basketstatus-badge{background-color:#83d01b;border-radius:50%;color:#fff;display:block;height:1.5rem;left:50%;position:absolute;text-align:center;top:0;transform:translateX(-38%);transition:all .35s;width:1.5rem}.esh-basketstatus-badge-inoperative{background-color:#f00;border-radius:50%;color:#fff;display:block;height:1.5rem;left:50%;position:absolute;text-align:center;top:0;transform:translateX(-38%);transition:all .35s;width:1.5rem}.esh-basketstatus:hover .esh-basketstatus-badge{background-color:transparent;color:#75b918;transition:all .35s} |
|||
@ -0,0 +1,57 @@ |
|||
@import '../../variables'; |
|||
|
|||
.esh-basketstatus { |
|||
cursor: pointer; |
|||
display: inline-block; |
|||
float: right; |
|||
position: relative; |
|||
transition: all $animation-speed-default; |
|||
|
|||
&.is-disabled { |
|||
opacity: .5; |
|||
pointer-events: none; |
|||
} |
|||
|
|||
&-image { |
|||
height: 36px; |
|||
margin-top: .5rem; |
|||
} |
|||
|
|||
&-badge { |
|||
$size: 1.5rem; |
|||
background-color: $color-secondary; |
|||
border-radius: 50%; |
|||
color: $color-foreground-brighter; |
|||
display: block; |
|||
height: $size; |
|||
left: 50%; |
|||
position: absolute; |
|||
text-align: center; |
|||
top: 0; |
|||
transform: translateX(-38%); |
|||
transition: all $animation-speed-default; |
|||
width: $size; |
|||
} |
|||
|
|||
&-badge-inoperative { |
|||
$size: 1.5rem; |
|||
background-color: $color-warning; |
|||
border-radius: 50%; |
|||
color: $color-foreground-brighter; |
|||
display: block; |
|||
height: $size; |
|||
left: 50%; |
|||
position: absolute; |
|||
text-align: center; |
|||
top: 0; |
|||
transform: translateX(-38%); |
|||
transition: all $animation-speed-default; |
|||
width: $size; |
|||
} |
|||
|
|||
&:hover &-badge { |
|||
background-color: transparent; |
|||
color: $color-secondary-dark; |
|||
transition: all $animation-speed-default; |
|||
} |
|||
} |
|||
@ -0,0 +1,49 @@ |
|||
.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; } |
|||
|
|||
@ -0,0 +1 @@ |
|||
.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 #eee;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:#fff;display:inline-block;font-size:1rem;font-weight:400;margin-top:1rem;padding:1rem 1.5rem;text-align:center;text-transform:uppercase;transition:all .35s}.esh-basket-checkout:hover{background-color:#4a760f;transition:all .35s} |
|||
@ -0,0 +1,89 @@ |
|||
@import '../variables'; |
|||
|
|||
@mixin margin-left($distance) { |
|||
margin-left: $distance; |
|||
} |
|||
|
|||
.esh-basket { |
|||
min-height: 80vh; |
|||
|
|||
&-titles { |
|||
padding-bottom: 1rem; |
|||
padding-top: 2rem; |
|||
|
|||
&--clean { |
|||
padding-bottom: 0; |
|||
padding-top: 0; |
|||
} |
|||
} |
|||
|
|||
&-title { |
|||
text-transform: uppercase; |
|||
} |
|||
|
|||
&-items { |
|||
&--border { |
|||
border-bottom: $border-light solid $color-foreground-bright; |
|||
padding: .5rem 0; |
|||
|
|||
&:last-of-type { |
|||
border-color: transparent; |
|||
} |
|||
} |
|||
|
|||
&-margin-left1 { |
|||
@include margin-left(1px); |
|||
} |
|||
} |
|||
|
|||
$item-height: 8rem; |
|||
|
|||
&-item { |
|||
font-size: $font-size-m; |
|||
font-weight: $font-weight-semilight; |
|||
|
|||
&--middle { |
|||
line-height: $item-height; |
|||
|
|||
@media screen and (max-width: $media-screen-m) { |
|||
line-height: $font-size-m; |
|||
} |
|||
} |
|||
|
|||
&--mark { |
|||
color: $color-brand; |
|||
} |
|||
} |
|||
|
|||
&-image { |
|||
height: $item-height; |
|||
} |
|||
|
|||
&-input { |
|||
line-height: 1rem; |
|||
width: 100%; |
|||
} |
|||
|
|||
&-checkout { |
|||
background-color: $color-secondary; |
|||
border: 0; |
|||
border-radius: 0; |
|||
color: $color-foreground-brighter; |
|||
display: inline-block; |
|||
font-size: 1rem; |
|||
font-weight: $font-weight-normal; |
|||
margin-top: 1rem; |
|||
padding: 1rem 1.5rem; |
|||
text-align: center; |
|||
text-transform: uppercase; |
|||
transition: all $animation-speed-default; |
|||
|
|||
&:hover { |
|||
background-color: $color-secondary-darker; |
|||
transition: all $animation-speed-default; |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
|
|||
@ -1,228 +1,117 @@ |
|||
.esh-catalog-hero { |
|||
.esh-catalog-hero { |
|||
background-image: url("../../images/main_banner.png"); |
|||
background-size: cover; |
|||
height: 260px; |
|||
width: 100%; |
|||
} |
|||
width: 100%; } |
|||
|
|||
.esh-catalog-title { |
|||
position: relative; |
|||
top: 74.28571px; |
|||
} |
|||
top: 74.28571px; } |
|||
|
|||
.esh-catalog-filters { |
|||
background-color: #00A69C; |
|||
height: 65px; |
|||
} |
|||
height: 65px; } |
|||
|
|||
.esh-catalog-filter { |
|||
-webkit-appearance: none; |
|||
background-color: transparent; |
|||
border-color: #00d9cc; |
|||
color: #FFFFFF; |
|||
cursor: pointer; |
|||
margin-right: 1rem; |
|||
margin-top: .5rem; |
|||
min-width: 140px; |
|||
outline-color: #83D01B; |
|||
padding-bottom: 0; |
|||
padding-left: 0.5rem; |
|||
padding-right: 0.5rem; |
|||
padding-top: 1.5rem; |
|||
min-width: 140px; |
|||
-webkit-appearance: none; |
|||
} |
|||
|
|||
padding-top: 1.5rem; } |
|||
.esh-catalog-filter option { |
|||
background-color: #00A69C; |
|||
} |
|||
background-color: #00A69C; } |
|||
|
|||
.esh-catalog-label { |
|||
display: inline-block; |
|||
position: relative; |
|||
z-index: 0; |
|||
} |
|||
|
|||
z-index: 0; } |
|||
.esh-catalog-label::before { |
|||
color: rgba(255, 255, 255, 0.5); |
|||
content: attr(data-title); |
|||
font-size: 0.65rem; |
|||
margin-top: 0.65rem; |
|||
margin-left: 0.5rem; |
|||
margin-top: 0.65rem; |
|||
position: absolute; |
|||
text-transform: uppercase; |
|||
z-index: 1; |
|||
} |
|||
|
|||
z-index: 1; } |
|||
.esh-catalog-label::after { |
|||
background-image: url("../../images/arrow-down.png"); |
|||
height: 7px; |
|||
content: ''; |
|||
height: 7px; |
|||
position: absolute; |
|||
right: 1.5rem; |
|||
top: 2.5rem; |
|||
width: 10px; |
|||
z-index: 1; |
|||
} |
|||
z-index: 1; } |
|||
|
|||
.esh-catalog-send { |
|||
background-color: #83D01B; |
|||
color: #FFFFFF; |
|||
cursor: pointer; |
|||
font-size: 1rem; |
|||
transform: translateY(.5rem); |
|||
margin-top: -1.5rem; |
|||
padding: 0.5rem; |
|||
transition: all 0.35s; |
|||
} |
|||
|
|||
transition: all 0.35s; } |
|||
.esh-catalog-send:hover { |
|||
background-color: #4a760f; |
|||
transition: all 0.35s; |
|||
} |
|||
transition: all 0.35s; } |
|||
|
|||
.esh-catalog-items { |
|||
margin-top: 1rem; |
|||
} |
|||
margin-top: 1rem; } |
|||
|
|||
.esh-catalog-item { |
|||
text-align: center; |
|||
margin-bottom: 1.5rem; |
|||
text-align: center; |
|||
width: 33%; |
|||
display: inline-block; |
|||
float: none !important; |
|||
} |
|||
|
|||
float: none !important; } |
|||
@media screen and (max-width: 1024px) { |
|||
.esh-catalog-item { |
|||
width: 50%; |
|||
} |
|||
} |
|||
|
|||
width: 50%; } } |
|||
@media screen and (max-width: 768px) { |
|||
.esh-catalog-item { |
|||
width: 100%; |
|||
} |
|||
} |
|||
width: 100%; } } |
|||
|
|||
.esh-catalog-thumbnail { |
|||
max-width: 370px; |
|||
width: 100%; |
|||
} |
|||
width: 100%; } |
|||
|
|||
.esh-catalog-button { |
|||
background-color: #83D01B; |
|||
border: none; |
|||
border: 0; |
|||
color: #FFFFFF; |
|||
cursor: pointer; |
|||
font-size: 1rem; |
|||
height: 3rem; |
|||
margin-top: 1rem; |
|||
transition: all 0.35s; |
|||
width: 80%; |
|||
} |
|||
width: 80%; } |
|||
.esh-catalog-button.is-disabled { |
|||
opacity: .5; |
|||
pointer-events: none; |
|||
} |
|||
|
|||
pointer-events: none; } |
|||
.esh-catalog-button:hover { |
|||
background-color: #4a760f; |
|||
transition: all 0.35s; |
|||
} |
|||
transition: all 0.35s; } |
|||
|
|||
.esh-catalog-name { |
|||
font-size: 1rem; |
|||
font-weight: 300; |
|||
margin-top: .5rem; |
|||
text-align: center; |
|||
text-transform: uppercase; |
|||
} |
|||
text-transform: uppercase; } |
|||
|
|||
.esh-catalog-price { |
|||
text-align: center; |
|||
font-weight: 900; |
|||
font-size: 28px; |
|||
} |
|||
|
|||
font-weight: 900; |
|||
text-align: center; } |
|||
.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; |
|||
} |
|||
@ -0,0 +1 @@ |
|||
.esh-catalog-hero{background-image:url("../../images/main_banner.png");background-size:cover;height:260px;width:100%}.esh-catalog-title{position:relative;top:74.28571px}.esh-catalog-filters{background-color:#00a69c;height:65px}.esh-catalog-filter{-webkit-appearance:none;background-color:transparent;border-color:#00d9cc;color:#fff;cursor:pointer;margin-right:1rem;margin-top:.5rem;min-width:140px;outline-color:#83d01b;padding-bottom:0;padding-left:.5rem;padding-right:.5rem;padding-top:1.5rem}.esh-catalog-filter option{background-color:#00a69c}.esh-catalog-label{display:inline-block;position:relative;z-index:0}.esh-catalog-label::before{color:rgba(255,255,255,.5);content:attr(data-title);font-size:.65rem;margin-left:.5rem;margin-top:.65rem;position:absolute;text-transform:uppercase;z-index:1}.esh-catalog-label::after{background-image:url("../../images/arrow-down.png");content:'';height:7px;position:absolute;right:1.5rem;top:2.5rem;width:10px;z-index:1}.esh-catalog-send{background-color:#83d01b;color:#fff;cursor:pointer;font-size:1rem;margin-top:-1.5rem;padding:.5rem;transition:all .35s}.esh-catalog-send:hover{background-color:#4a760f;transition:all .35s}.esh-catalog-items{margin-top:1rem}.esh-catalog-item{margin-bottom:1.5rem;text-align:center;width:33%;display:inline-block;float:none !important}@media screen and (max-width:1024px){.esh-catalog-item{width:50%}}@media screen and (max-width:768px){.esh-catalog-item{width:100%}}.esh-catalog-thumbnail{max-width:370px;width:100%}.esh-catalog-button{background-color:#83d01b;border:0;color:#fff;cursor:pointer;font-size:1rem;height:3rem;margin-top:1rem;transition:all .35s;width:80%}.esh-catalog-button.is-disabled{opacity:.5;pointer-events:none}.esh-catalog-button:hover{background-color:#4a760f;transition:all .35s}.esh-catalog-name{font-size:1rem;font-weight:300;margin-top:.5rem;text-align:center;text-transform:uppercase}.esh-catalog-price{font-size:28px;font-weight:900;text-align:center}.esh-catalog-price::before{content:'$'} |
|||
@ -0,0 +1,154 @@ |
|||
@import '../variables'; |
|||
|
|||
.esh-catalog { |
|||
$banner-height: 260px; |
|||
|
|||
&-hero { |
|||
background-image: url($image-main_banner); |
|||
background-size: cover; |
|||
height: $banner-height; |
|||
width: 100%; |
|||
} |
|||
|
|||
&-title { |
|||
position: relative; |
|||
top: $banner-height / 3.5; |
|||
} |
|||
|
|||
$filter-height: 65px; |
|||
|
|||
&-filters { |
|||
background-color: $color-brand; |
|||
height: $filter-height; |
|||
} |
|||
|
|||
$filter-padding: .5rem; |
|||
|
|||
&-filter { |
|||
-webkit-appearance: none; |
|||
background-color: transparent; |
|||
border-color: $color-brand-bright; |
|||
color: $color-foreground-brighter; |
|||
cursor: pointer; |
|||
margin-right: 1rem; |
|||
margin-top: .5rem; |
|||
min-width: 140px; |
|||
outline-color: $color-secondary; |
|||
padding-bottom: 0; |
|||
padding-left: $filter-padding; |
|||
padding-right: $filter-padding; |
|||
padding-top: $filter-padding * 3; |
|||
|
|||
option { |
|||
background-color: $color-brand; |
|||
} |
|||
} |
|||
|
|||
&-label { |
|||
display: inline-block; |
|||
position: relative; |
|||
z-index: 0; |
|||
|
|||
&::before { |
|||
color: rgba($color-foreground-brighter, .5); |
|||
content: attr(data-title); |
|||
font-size: $font-size-xs; |
|||
margin-left: $filter-padding; |
|||
margin-top: $font-size-xs; |
|||
position: absolute; |
|||
text-transform: uppercase; |
|||
z-index: 1; |
|||
} |
|||
|
|||
&::after { |
|||
background-image: url($image-arrow_down); |
|||
content: ''; |
|||
height: 7px; //png height |
|||
position: absolute; |
|||
right: $filter-padding * 3; |
|||
top: $filter-padding * 5; |
|||
width: 10px; //png width |
|||
z-index: 1; |
|||
} |
|||
} |
|||
|
|||
&-send { |
|||
background-color: $color-secondary; |
|||
color: $color-foreground-brighter; |
|||
cursor: pointer; |
|||
font-size: $font-size-m; |
|||
margin-top: -$filter-padding * 3; |
|||
padding: $filter-padding; |
|||
transition: all $animation-speed-default; |
|||
|
|||
&:hover { |
|||
background-color: $color-secondary-darker; |
|||
transition: all $animation-speed-default; |
|||
} |
|||
} |
|||
|
|||
&-items { |
|||
margin-top: 1rem; |
|||
} |
|||
|
|||
&-item { |
|||
margin-bottom: 1.5rem; |
|||
text-align: center; |
|||
width: 33%; |
|||
display: inline-block; |
|||
float: none !important; |
|||
|
|||
@media screen and (max-width: $media-screen-m) { |
|||
width: 50%; |
|||
} |
|||
|
|||
@media screen and (max-width: $media-screen-s) { |
|||
width: 100%; |
|||
} |
|||
} |
|||
|
|||
&-thumbnail { |
|||
max-width: 370px; |
|||
width: 100%; |
|||
} |
|||
|
|||
&-button { |
|||
background-color: $color-secondary; |
|||
border: 0; |
|||
color: $color-foreground-brighter; |
|||
cursor: pointer; |
|||
font-size: $font-size-m; |
|||
height: 3rem; |
|||
margin-top: 1rem; |
|||
transition: all $animation-speed-default; |
|||
width: 80%; |
|||
|
|||
&.is-disabled { |
|||
opacity: .5; |
|||
pointer-events: none; |
|||
} |
|||
|
|||
&:hover { |
|||
background-color: $color-secondary-darker; |
|||
transition: all $animation-speed-default; |
|||
} |
|||
} |
|||
|
|||
&-name { |
|||
font-size: $font-size-m; |
|||
font-weight: $font-weight-semilight; |
|||
margin-top: .5rem; |
|||
text-align: center; |
|||
text-transform: uppercase; |
|||
} |
|||
|
|||
&-price { |
|||
font-size: 28px; |
|||
font-weight: 900; |
|||
text-align: center; |
|||
|
|||
&::before { |
|||
content: '$'; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,50 @@ |
|||
.esh-orders { |
|||
min-height: 80vh; |
|||
overflow-x: hidden; } |
|||
.esh-orders-header { |
|||
background-color: #00A69C; |
|||
height: 4rem; } |
|||
.esh-orders-back { |
|||
color: rgba(255, 255, 255, 0.4); |
|||
line-height: 4rem; |
|||
text-decoration: none; |
|||
text-transform: uppercase; |
|||
transition: color 0.35s; } |
|||
.esh-orders-back:hover { |
|||
color: #FFFFFF; |
|||
transition: color 0.35s; } |
|||
.esh-orders-titles { |
|||
padding-bottom: 1rem; |
|||
padding-top: 2rem; } |
|||
.esh-orders-title { |
|||
text-transform: uppercase; } |
|||
.esh-orders-items { |
|||
height: 2rem; |
|||
line-height: 2rem; |
|||
position: relative; } |
|||
.esh-orders-items:nth-of-type(2n + 1):before { |
|||
background-color: #EEEEFF; |
|||
content: ''; |
|||
height: 100%; |
|||
left: 0; |
|||
margin-left: -100vw; |
|||
position: absolute; |
|||
top: 0; |
|||
width: 200vw; |
|||
z-index: -1; } |
|||
.esh-orders-item { |
|||
font-weight: 300; } |
|||
.esh-orders-item--hover { |
|||
opacity: 0; |
|||
pointer-events: none; } |
|||
.esh-orders-items:hover .esh-orders-item--hover { |
|||
opacity: 1; |
|||
pointer-events: all; } |
|||
.esh-orders-link { |
|||
color: #83D01B; |
|||
text-decoration: none; |
|||
transition: color 0.35s; } |
|||
.esh-orders-link:hover { |
|||
color: #75b918; |
|||
transition: color 0.35s; } |
|||
|
|||
@ -0,0 +1 @@ |
|||
.esh-orders{min-height:80vh;overflow-x:hidden}.esh-orders-header{background-color:#00a69c;height:4rem}.esh-orders-back{color:rgba(255,255,255,.4);line-height:4rem;text-decoration:none;text-transform:uppercase;transition:color .35s}.esh-orders-back:hover{color:#fff;transition:color .35s}.esh-orders-titles{padding-bottom:1rem;padding-top:2rem}.esh-orders-title{text-transform:uppercase}.esh-orders-items{height:2rem;line-height:2rem;position:relative}.esh-orders-items:nth-of-type(2n+1):before{background-color:#eef;content:'';height:100%;left:0;margin-left:-100vw;position:absolute;top:0;width:200vw;z-index:-1}.esh-orders-item{font-weight:300}.esh-orders-item--hover{opacity:0;pointer-events:none}.esh-orders-items:hover .esh-orders-item--hover{opacity:1;pointer-events:all}.esh-orders-link{color:#83d01b;text-decoration:none;transition:color .35s}.esh-orders-link:hover{color:#75b918;transition:color .35s} |
|||
@ -0,0 +1,91 @@ |
|||
@import '../variables'; |
|||
|
|||
.esh-orders { |
|||
min-height: 80vh; |
|||
overflow-x: hidden; |
|||
$header-height: 4rem; |
|||
&-header |
|||
|
|||
{ |
|||
background-color: #00A69C; |
|||
height: $header-height; |
|||
} |
|||
|
|||
&-back { |
|||
color: rgba($color-foreground-brighter, .4); |
|||
line-height: $header-height; |
|||
text-decoration: none; |
|||
text-transform: uppercase; |
|||
transition: color $animation-speed-default; |
|||
&:hover |
|||
|
|||
{ |
|||
color: $color-foreground-brighter; |
|||
transition: color $animation-speed-default; |
|||
} |
|||
|
|||
} |
|||
|
|||
&-titles { |
|||
padding-bottom: 1rem; |
|||
padding-top: 2rem; |
|||
} |
|||
|
|||
&-title { |
|||
text-transform: uppercase; |
|||
} |
|||
|
|||
&-items { |
|||
$height: 2rem; |
|||
height: $height; |
|||
line-height: $height; |
|||
position: relative; |
|||
&:nth-of-type(2n + 1) |
|||
|
|||
{ |
|||
&:before |
|||
|
|||
{ |
|||
background-color: $color-background-bright; |
|||
content: ''; |
|||
height: 100%; |
|||
left: 0; |
|||
margin-left: -100vw; |
|||
position: absolute; |
|||
top: 0; |
|||
width: 200vw; |
|||
z-index: -1; |
|||
} |
|||
|
|||
} |
|||
} |
|||
|
|||
&-item { |
|||
font-weight: $font-weight-semilight; |
|||
&--hover |
|||
|
|||
{ |
|||
opacity: 0; |
|||
pointer-events: none; |
|||
} |
|||
|
|||
} |
|||
|
|||
&-items:hover &-item--hover { |
|||
opacity: 1; |
|||
pointer-events: all; |
|||
} |
|||
|
|||
&-link { |
|||
color: $color-secondary; |
|||
text-decoration: none; |
|||
transition: color $animation-speed-default; |
|||
&:hover |
|||
|
|||
{ |
|||
color: $color-secondary-dark; |
|||
transition: color $animation-speed-default; |
|||
} |
|||
|
|||
} |
|||
} |
|||
|
Before Width: | Height: | Size: 5.1 KiB |
@ -1,47 +0,0 @@ |
|||
using Infrastructure.FileSystem; |
|||
using Microsoft.AspNetCore.Hosting; |
|||
using System.IO; |
|||
using Xunit; |
|||
using Moq; |
|||
|
|||
namespace IntegrationTests.Infrastructure.File |
|||
{ |
|||
public class LocalFileImageServiceGetImageBytesById |
|||
{ |
|||
private byte[] _testBytes = new byte[] { 0x01, 0x02, 0x03 }; |
|||
private readonly Mock<IHostingEnvironment> _mockEnvironment = new Mock<IHostingEnvironment>(); |
|||
private int _testImageId = 123; |
|||
private string _testFileName = "123.png"; |
|||
|
|||
public LocalFileImageServiceGetImageBytesById() |
|||
{ |
|||
// create folder if necessary
|
|||
Directory.CreateDirectory(Path.Combine(GetFileDirectory(), "Pics")); |
|||
|
|||
string filePath = GetFilePath(_testFileName); |
|||
System.IO.File.WriteAllBytes(filePath, _testBytes); |
|||
_mockEnvironment.SetupGet<string>(m => m.ContentRootPath).Returns(GetFileDirectory()); |
|||
} |
|||
|
|||
private string GetFilePath(string fileName) |
|||
{ |
|||
return Path.Combine(GetFileDirectory(), "Pics", fileName); |
|||
} |
|||
|
|||
private string GetFileDirectory() |
|||
{ |
|||
var location = System.Reflection.Assembly.GetEntryAssembly().Location; |
|||
return Path.GetDirectoryName(location); |
|||
} |
|||
|
|||
[Fact] |
|||
public void ReturnsFileContentResultGivenValidId() |
|||
{ |
|||
var fileService = new LocalFileImageService(_mockEnvironment.Object); |
|||
|
|||
var result = fileService.GetImageBytesById(_testImageId); |
|||
|
|||
Assert.Equal(_testBytes, result); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue