Initial Upgrade to .NET Core 2.0 (#50)
* 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.
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard1.4</TargetFramework>
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
26
src/ApplicationCore/Entities/BuyerAggregate/Buyer.cs
Normal file
26
src/ApplicationCore/Entities/BuyerAggregate/Buyer.cs
Normal file
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
11
src/ApplicationCore/Entities/BuyerAggregate/PaymentMethod.cs
Normal file
11
src/ApplicationCore/Entities/BuyerAggregate/PaymentMethod.cs
Normal file
@@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
39
src/ApplicationCore/Entities/OrderAggregate/Address.cs
Normal file
39
src/ApplicationCore/Entities/OrderAggregate/Address.cs
Normal file
@@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
48
src/ApplicationCore/Entities/OrderAggregate/Order.cs
Normal file
48
src/ApplicationCore/Entities/OrderAggregate/Order.cs
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
22
src/ApplicationCore/Entities/OrderAggregate/OrderItem.cs
Normal file
22
src/ApplicationCore/Entities/OrderAggregate/OrderItem.cs
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
5
src/ApplicationCore/Interfaces/IAggregateRoot.cs
Normal file
5
src/ApplicationCore/Interfaces/IAggregateRoot.cs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
namespace ApplicationCore.Interfaces
|
||||||
|
{
|
||||||
|
public interface IAggregateRoot
|
||||||
|
{ }
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
/// <typeparam name="T"></typeparam>
|
/// <typeparam name="T"></typeparam>
|
||||||
public interface IAppLogger<T>
|
public interface IAppLogger<T>
|
||||||
{
|
{
|
||||||
|
void LogInformation(string message, params object[] args);
|
||||||
void LogWarning(string message, params object[] args);
|
void LogWarning(string message, params object[] args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
16
src/ApplicationCore/Interfaces/IAsyncRepository.cs
Normal file
16
src/ApplicationCore/Interfaces/IAsyncRepository.cs
Normal file
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
12
src/ApplicationCore/Interfaces/IOrderRepository.cs
Normal file
12
src/ApplicationCore/Interfaces/IOrderRepository.cs
Normal file
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
10
src/ApplicationCore/Interfaces/IOrderService.cs
Normal file
10
src/ApplicationCore/Interfaces/IOrderService.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
using ApplicationCore.Entities.OrderAggregate;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace ApplicationCore.Interfaces
|
||||||
|
{
|
||||||
|
public interface IOrderService
|
||||||
|
{
|
||||||
|
Task CreateOrderAsync(int basketId, Address shippingAddress);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +1,14 @@
|
|||||||
using Microsoft.eShopWeb.ApplicationCore.Entities;
|
using Microsoft.eShopWeb.ApplicationCore.Entities;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq.Expressions;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace ApplicationCore.Interfaces
|
namespace ApplicationCore.Interfaces
|
||||||
{
|
{
|
||||||
|
|
||||||
public interface IRepository<T> where T : BaseEntity
|
public interface IRepository<T> where T : BaseEntity
|
||||||
{
|
{
|
||||||
T GetById(int id);
|
T GetById(int id);
|
||||||
List<T> List();
|
IEnumerable<T> ListAll();
|
||||||
List<T> List(ISpecification<T> spec);
|
IEnumerable<T> List(ISpecification<T> spec);
|
||||||
T Add(T entity);
|
T Add(T entity);
|
||||||
void Update(T entity);
|
void Update(T entity);
|
||||||
void Delete(T entity);
|
void Delete(T entity);
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ namespace ApplicationCore.Interfaces
|
|||||||
{
|
{
|
||||||
Expression<Func<T, bool>> Criteria { get; }
|
Expression<Func<T, bool>> Criteria { get; }
|
||||||
List<Expression<Func<T, object>>> Includes { get; }
|
List<Expression<Func<T, object>>> Includes { get; }
|
||||||
|
List<string> IncludeStrings { get; }
|
||||||
void AddInclude(Expression<Func<T, object>> includeExpression);
|
void AddInclude(Expression<Func<T, object>> includeExpression);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
using Microsoft.eShopWeb.ApplicationCore.Entities;
|
namespace ApplicationCore.Interfaces
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace ApplicationCore.Interfaces
|
|
||||||
{
|
{
|
||||||
|
|
||||||
public interface IUriComposer
|
public interface IUriComposer
|
||||||
{
|
{
|
||||||
string ComposePicUri(string uriTemplate);
|
string ComposePicUri(string uriTemplate);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using Microsoft.eShopWeb.ApplicationCore.Entities;
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using ApplicationCore.Entities.OrderAggregate;
|
||||||
|
|
||||||
namespace ApplicationCore.Specifications
|
namespace ApplicationCore.Specifications
|
||||||
{
|
{
|
||||||
@@ -28,6 +29,8 @@ namespace ApplicationCore.Specifications
|
|||||||
|
|
||||||
public List<Expression<Func<Basket, object>>> Includes { get; } = new List<Expression<Func<Basket, object>>>();
|
public List<Expression<Func<Basket, object>>> Includes { get; } = new List<Expression<Func<Basket, object>>>();
|
||||||
|
|
||||||
|
public List<string> IncludeStrings { get; } = new List<string>();
|
||||||
|
|
||||||
public void AddInclude(Expression<Func<Basket, object>> includeExpression)
|
public void AddInclude(Expression<Func<Basket, object>> includeExpression)
|
||||||
{
|
{
|
||||||
Includes.Add(includeExpression);
|
Includes.Add(includeExpression);
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ namespace ApplicationCore.Specifications
|
|||||||
|
|
||||||
public List<Expression<Func<CatalogItem, object>>> Includes { get; } = new List<Expression<Func<CatalogItem, object>>>();
|
public List<Expression<Func<CatalogItem, object>>> Includes { get; } = new List<Expression<Func<CatalogItem, object>>>();
|
||||||
|
|
||||||
|
public List<string> IncludeStrings { get; } = new List<string>();
|
||||||
|
|
||||||
public void AddInclude(Expression<Func<CatalogItem, object>> includeExpression)
|
public void AddInclude(Expression<Func<CatalogItem, object>> includeExpression)
|
||||||
{
|
{
|
||||||
Includes.Add(includeExpression);
|
Includes.Add(includeExpression);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ using Microsoft.EntityFrameworkCore;
|
|||||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
using Microsoft.eShopWeb.ApplicationCore.Entities;
|
using Microsoft.eShopWeb.ApplicationCore.Entities;
|
||||||
using Microsoft.EntityFrameworkCore.Metadata;
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
|
using ApplicationCore.Entities.OrderAggregate;
|
||||||
|
|
||||||
namespace Infrastructure.Data
|
namespace Infrastructure.Data
|
||||||
{
|
{
|
||||||
@@ -20,6 +21,8 @@ namespace Infrastructure.Data
|
|||||||
public DbSet<CatalogItem> CatalogItems { get; set; }
|
public DbSet<CatalogItem> CatalogItems { get; set; }
|
||||||
public DbSet<CatalogBrand> CatalogBrands { get; set; }
|
public DbSet<CatalogBrand> CatalogBrands { get; set; }
|
||||||
public DbSet<CatalogType> CatalogTypes { get; set; }
|
public DbSet<CatalogType> CatalogTypes { get; set; }
|
||||||
|
public DbSet<Order> Orders { get; set; }
|
||||||
|
public DbSet<OrderItem> OrderItems { get; set; }
|
||||||
|
|
||||||
protected override void OnModelCreating(ModelBuilder builder)
|
protected override void OnModelCreating(ModelBuilder builder)
|
||||||
{
|
{
|
||||||
@@ -27,6 +30,8 @@ namespace Infrastructure.Data
|
|||||||
builder.Entity<CatalogBrand>(ConfigureCatalogBrand);
|
builder.Entity<CatalogBrand>(ConfigureCatalogBrand);
|
||||||
builder.Entity<CatalogType>(ConfigureCatalogType);
|
builder.Entity<CatalogType>(ConfigureCatalogType);
|
||||||
builder.Entity<CatalogItem>(ConfigureCatalogItem);
|
builder.Entity<CatalogItem>(ConfigureCatalogItem);
|
||||||
|
builder.Entity<Order>(ConfigureOrder);
|
||||||
|
builder.Entity<OrderItem>(ConfigureOrderItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ConfigureBasket(EntityTypeBuilder<Basket> builder)
|
private void ConfigureBasket(EntityTypeBuilder<Basket> builder)
|
||||||
@@ -36,7 +41,7 @@ namespace Infrastructure.Data
|
|||||||
navigation.SetPropertyAccessMode(PropertyAccessMode.Field);
|
navigation.SetPropertyAccessMode(PropertyAccessMode.Field);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConfigureCatalogItem(EntityTypeBuilder<CatalogItem> builder)
|
private void ConfigureCatalogItem(EntityTypeBuilder<CatalogItem> builder)
|
||||||
{
|
{
|
||||||
builder.ToTable("Catalog");
|
builder.ToTable("Catalog");
|
||||||
|
|
||||||
@@ -63,7 +68,7 @@ namespace Infrastructure.Data
|
|||||||
.HasForeignKey(ci => ci.CatalogTypeId);
|
.HasForeignKey(ci => ci.CatalogTypeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConfigureCatalogBrand(EntityTypeBuilder<CatalogBrand> builder)
|
private void ConfigureCatalogBrand(EntityTypeBuilder<CatalogBrand> builder)
|
||||||
{
|
{
|
||||||
builder.ToTable("CatalogBrand");
|
builder.ToTable("CatalogBrand");
|
||||||
|
|
||||||
@@ -78,7 +83,7 @@ namespace Infrastructure.Data
|
|||||||
.HasMaxLength(100);
|
.HasMaxLength(100);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConfigureCatalogType(EntityTypeBuilder<CatalogType> builder)
|
private void ConfigureCatalogType(EntityTypeBuilder<CatalogType> builder)
|
||||||
{
|
{
|
||||||
builder.ToTable("CatalogType");
|
builder.ToTable("CatalogType");
|
||||||
|
|
||||||
@@ -92,5 +97,14 @@ namespace Infrastructure.Data
|
|||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(100);
|
.HasMaxLength(100);
|
||||||
}
|
}
|
||||||
|
private void ConfigureOrder(EntityTypeBuilder<Order> builder)
|
||||||
|
{
|
||||||
|
builder.OwnsOne(o => o.ShipToAddress);
|
||||||
|
}
|
||||||
|
private void ConfigureOrderItem(EntityTypeBuilder<OrderItem> builder)
|
||||||
|
{
|
||||||
|
builder.OwnsOne(i => i.ItemOrdered);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,39 +10,38 @@ namespace Infrastructure.Data
|
|||||||
{
|
{
|
||||||
public class CatalogContextSeed
|
public class CatalogContextSeed
|
||||||
{
|
{
|
||||||
public static async Task SeedAsync(IApplicationBuilder applicationBuilder, ILoggerFactory loggerFactory, int? retry = 0)
|
public static async Task SeedAsync(IApplicationBuilder applicationBuilder,
|
||||||
|
CatalogContext catalogContext,
|
||||||
|
ILoggerFactory loggerFactory, int? retry = 0)
|
||||||
{
|
{
|
||||||
int retryForAvailability = retry.Value;
|
int retryForAvailability = retry.Value;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var context = (CatalogContext)applicationBuilder
|
|
||||||
.ApplicationServices.GetService(typeof(CatalogContext));
|
|
||||||
|
|
||||||
// TODO: Only run this if using a real database
|
// TODO: Only run this if using a real database
|
||||||
// context.Database.Migrate();
|
// context.Database.Migrate();
|
||||||
|
|
||||||
if (!context.CatalogBrands.Any())
|
if (!catalogContext.CatalogBrands.Any())
|
||||||
{
|
{
|
||||||
context.CatalogBrands.AddRange(
|
catalogContext.CatalogBrands.AddRange(
|
||||||
GetPreconfiguredCatalogBrands());
|
GetPreconfiguredCatalogBrands());
|
||||||
|
|
||||||
await context.SaveChangesAsync();
|
await catalogContext.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!context.CatalogTypes.Any())
|
if (!catalogContext.CatalogTypes.Any())
|
||||||
{
|
{
|
||||||
context.CatalogTypes.AddRange(
|
catalogContext.CatalogTypes.AddRange(
|
||||||
GetPreconfiguredCatalogTypes());
|
GetPreconfiguredCatalogTypes());
|
||||||
|
|
||||||
await context.SaveChangesAsync();
|
await catalogContext.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!context.CatalogItems.Any())
|
if (!catalogContext.CatalogItems.Any())
|
||||||
{
|
{
|
||||||
context.CatalogItems.AddRange(
|
catalogContext.CatalogItems.AddRange(
|
||||||
GetPreconfiguredItems());
|
GetPreconfiguredItems());
|
||||||
|
|
||||||
await context.SaveChangesAsync();
|
await catalogContext.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -50,9 +49,9 @@ namespace Infrastructure.Data
|
|||||||
if (retryForAvailability < 10)
|
if (retryForAvailability < 10)
|
||||||
{
|
{
|
||||||
retryForAvailability++;
|
retryForAvailability++;
|
||||||
var log = loggerFactory.CreateLogger("catalog seed");
|
var log = loggerFactory.CreateLogger<CatalogContextSeed>();
|
||||||
log.LogError(ex.Message);
|
log.LogError(ex.Message);
|
||||||
await SeedAsync(applicationBuilder, loggerFactory, retryForAvailability);
|
await SeedAsync(applicationBuilder, catalogContext, loggerFactory, retryForAvailability);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,36 +3,68 @@ using Microsoft.EntityFrameworkCore;
|
|||||||
using Microsoft.eShopWeb.ApplicationCore.Entities;
|
using Microsoft.eShopWeb.ApplicationCore.Entities;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Infrastructure.Data
|
namespace Infrastructure.Data
|
||||||
{
|
{
|
||||||
public class EfRepository<T> : IRepository<T> where T : BaseEntity
|
/// <summary>
|
||||||
|
/// "There's some repetition here - couldn't we have some the sync methods call the async?"
|
||||||
|
/// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
public class EfRepository<T> : IRepository<T>, IAsyncRepository<T> where T : BaseEntity
|
||||||
{
|
{
|
||||||
private readonly CatalogContext _dbContext;
|
protected readonly CatalogContext _dbContext;
|
||||||
|
|
||||||
public EfRepository(CatalogContext dbContext)
|
public EfRepository(CatalogContext dbContext)
|
||||||
{
|
{
|
||||||
_dbContext = dbContext;
|
_dbContext = dbContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
public T GetById(int id)
|
public virtual T GetById(int id)
|
||||||
{
|
{
|
||||||
return _dbContext.Set<T>().SingleOrDefault(e => e.Id == id);
|
return _dbContext.Set<T>().Find(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<T> List()
|
public virtual async Task<T> GetByIdAsync(int id)
|
||||||
{
|
{
|
||||||
return _dbContext.Set<T>().ToList();
|
return await _dbContext.Set<T>().FindAsync(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<T> List(ISpecification<T> spec)
|
public IEnumerable<T> ListAll()
|
||||||
|
{
|
||||||
|
return _dbContext.Set<T>().AsEnumerable();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<T>> ListAllAsync()
|
||||||
|
{
|
||||||
|
return await _dbContext.Set<T>().ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<T> List(ISpecification<T> spec)
|
||||||
{
|
{
|
||||||
var queryableResultWithIncludes = spec.Includes
|
var queryableResultWithIncludes = spec.Includes
|
||||||
.Aggregate(_dbContext.Set<T>().AsQueryable(),
|
.Aggregate(_dbContext.Set<T>().AsQueryable(),
|
||||||
(current, include) => current.Include(include));
|
(current, include) => current.Include(include));
|
||||||
return queryableResultWithIncludes
|
var secondaryResult = spec.IncludeStrings
|
||||||
|
.Aggregate(queryableResultWithIncludes,
|
||||||
|
(current, include) => current.Include(include));
|
||||||
|
return secondaryResult
|
||||||
.Where(spec.Criteria)
|
.Where(spec.Criteria)
|
||||||
.ToList();
|
.AsEnumerable();
|
||||||
|
}
|
||||||
|
public async Task<List<T>> ListAsync(ISpecification<T> spec)
|
||||||
|
{
|
||||||
|
var queryableResultWithIncludes = spec.Includes
|
||||||
|
.Aggregate(_dbContext.Set<T>().AsQueryable(),
|
||||||
|
(current, include) => current.Include(include));
|
||||||
|
var secondaryResult = spec.IncludeStrings
|
||||||
|
.Aggregate(queryableResultWithIncludes,
|
||||||
|
(current, include) => current.Include(include));
|
||||||
|
|
||||||
|
return await secondaryResult
|
||||||
|
.Where(spec.Criteria)
|
||||||
|
.ToListAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public T Add(T entity)
|
public T Add(T entity)
|
||||||
@@ -43,10 +75,12 @@ namespace Infrastructure.Data
|
|||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Delete(T entity)
|
public async Task<T> AddAsync(T entity)
|
||||||
{
|
{
|
||||||
_dbContext.Set<T>().Remove(entity);
|
_dbContext.Set<T>().Add(entity);
|
||||||
_dbContext.SaveChanges();
|
await _dbContext.SaveChangesAsync();
|
||||||
|
|
||||||
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Update(T entity)
|
public void Update(T entity)
|
||||||
@@ -54,6 +88,21 @@ namespace Infrastructure.Data
|
|||||||
_dbContext.Entry(entity).State = EntityState.Modified;
|
_dbContext.Entry(entity).State = EntityState.Modified;
|
||||||
_dbContext.SaveChanges();
|
_dbContext.SaveChanges();
|
||||||
}
|
}
|
||||||
|
public async Task UpdateAsync(T entity)
|
||||||
|
{
|
||||||
|
_dbContext.Entry(entity).State = EntityState.Modified;
|
||||||
|
await _dbContext.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Delete(T entity)
|
||||||
|
{
|
||||||
|
_dbContext.Set<T>().Remove(entity);
|
||||||
|
_dbContext.SaveChanges();
|
||||||
|
}
|
||||||
|
public async Task DeleteAsync(T entity)
|
||||||
|
{
|
||||||
|
_dbContext.Set<T>().Remove(entity);
|
||||||
|
await _dbContext.SaveChangesAsync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
31
src/Infrastructure/Data/OrderRepository.cs
Normal file
31
src/Infrastructure/Data/OrderRepository.cs
Normal file
@@ -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,5 +1,4 @@
|
|||||||
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
|
|
||||||
|
|
||||||
namespace Infrastructure.Identity
|
namespace Infrastructure.Identity
|
||||||
|
|||||||
@@ -1,30 +1,26 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard1.4</TargetFramework>
|
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore" Version="1.1.2" />
|
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="1.1.2" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.0.0" PrivateAssets="All" />
|
||||||
<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" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\ApplicationCore\ApplicationCore.csproj" />
|
<ProjectReference Include="..\ApplicationCore\ApplicationCore.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools" Version="1.1.1" />
|
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.0.0" />
|
||||||
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.0.1" />
|
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.0" />
|
||||||
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Design" Version="1.1.2" />
|
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="Data\Migrations\" />
|
<Folder Include="Data\Migrations\" />
|
||||||
<Folder Include="Services\" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
@@ -10,9 +10,14 @@ namespace Infrastructure.Logging
|
|||||||
{
|
{
|
||||||
_logger = loggerFactory.CreateLogger<T>();
|
_logger = loggerFactory.CreateLogger<T>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LogWarning(string message, params object[] args)
|
public void LogWarning(string message, params object[] args)
|
||||||
{
|
{
|
||||||
_logger.LogWarning(message, args);
|
_logger.LogWarning(message, args);
|
||||||
}
|
}
|
||||||
|
public void LogInformation(string message, params object[] args)
|
||||||
|
{
|
||||||
|
_logger.LogInformation(message, args);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
40
src/Infrastructure/Services/OrderService.cs
Normal file
40
src/Infrastructure/Services/OrderService.cs
Normal file
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,12 +3,11 @@ using Microsoft.AspNetCore.Mvc;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
using Infrastructure.Identity;
|
using Infrastructure.Identity;
|
||||||
using System;
|
using System;
|
||||||
using Microsoft.eShopWeb.ApplicationCore.Entities;
|
|
||||||
using ApplicationCore.Interfaces;
|
using ApplicationCore.Interfaces;
|
||||||
using Web;
|
using Web;
|
||||||
|
using Microsoft.AspNetCore.Authentication;
|
||||||
|
|
||||||
namespace Microsoft.eShopWeb.Controllers
|
namespace Microsoft.eShopWeb.Controllers
|
||||||
{
|
{
|
||||||
@@ -17,18 +16,15 @@ namespace Microsoft.eShopWeb.Controllers
|
|||||||
{
|
{
|
||||||
private readonly UserManager<ApplicationUser> _userManager;
|
private readonly UserManager<ApplicationUser> _userManager;
|
||||||
private readonly SignInManager<ApplicationUser> _signInManager;
|
private readonly SignInManager<ApplicationUser> _signInManager;
|
||||||
private readonly string _externalCookieScheme;
|
|
||||||
private readonly IBasketService _basketService;
|
private readonly IBasketService _basketService;
|
||||||
|
|
||||||
public AccountController(
|
public AccountController(
|
||||||
UserManager<ApplicationUser> userManager,
|
UserManager<ApplicationUser> userManager,
|
||||||
SignInManager<ApplicationUser> signInManager,
|
SignInManager<ApplicationUser> signInManager,
|
||||||
IOptions<IdentityCookieOptions> identityCookieOptions,
|
|
||||||
IBasketService basketService)
|
IBasketService basketService)
|
||||||
{
|
{
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
_signInManager = signInManager;
|
_signInManager = signInManager;
|
||||||
_externalCookieScheme = identityCookieOptions.Value.ExternalCookieAuthenticationScheme;
|
|
||||||
_basketService = basketService;
|
_basketService = basketService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,9 +33,15 @@ namespace Microsoft.eShopWeb.Controllers
|
|||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
public async Task<IActionResult> SignIn(string returnUrl = null)
|
public async Task<IActionResult> SignIn(string returnUrl = null)
|
||||||
{
|
{
|
||||||
await HttpContext.Authentication.SignOutAsync(_externalCookieScheme);
|
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
|
||||||
|
|
||||||
ViewData["ReturnUrl"] = returnUrl;
|
ViewData["ReturnUrl"] = returnUrl;
|
||||||
|
if (!String.IsNullOrEmpty(returnUrl) &&
|
||||||
|
returnUrl.ToLower().Contains("checkout"))
|
||||||
|
{
|
||||||
|
ViewData["ReturnUrl"] = "/Basket/Index";
|
||||||
|
}
|
||||||
|
|
||||||
return View();
|
return View();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,7 +63,7 @@ namespace Microsoft.eShopWeb.Controllers
|
|||||||
string anonymousBasketId = Request.Cookies[Constants.BASKET_COOKIENAME];
|
string anonymousBasketId = Request.Cookies[Constants.BASKET_COOKIENAME];
|
||||||
if (!String.IsNullOrEmpty(anonymousBasketId))
|
if (!String.IsNullOrEmpty(anonymousBasketId))
|
||||||
{
|
{
|
||||||
_basketService.TransferBasket(anonymousBasketId, model.Email);
|
await _basketService.TransferBasketAsync(anonymousBasketId, model.Email);
|
||||||
Response.Cookies.Delete(Constants.BASKET_COOKIENAME);
|
Response.Cookies.Delete(Constants.BASKET_COOKIENAME);
|
||||||
}
|
}
|
||||||
return RedirectToLocal(returnUrl);
|
return RedirectToLocal(returnUrl);
|
||||||
@@ -74,7 +76,6 @@ namespace Microsoft.eShopWeb.Controllers
|
|||||||
[ValidateAntiForgeryToken]
|
[ValidateAntiForgeryToken]
|
||||||
public async Task<ActionResult> SignOut()
|
public async Task<ActionResult> SignOut()
|
||||||
{
|
{
|
||||||
HttpContext.Session.Clear();
|
|
||||||
await _signInManager.SignOutAsync();
|
await _signInManager.SignOutAsync();
|
||||||
|
|
||||||
return RedirectToAction(nameof(CatalogController.Index), "Catalog");
|
return RedirectToAction(nameof(CatalogController.Index), "Catalog");
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ using Microsoft.AspNetCore.Identity;
|
|||||||
using Infrastructure.Identity;
|
using Infrastructure.Identity;
|
||||||
using System;
|
using System;
|
||||||
using Web;
|
using Web;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using ApplicationCore.Entities.OrderAggregate;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
|
||||||
namespace Microsoft.eShopWeb.Controllers
|
namespace Microsoft.eShopWeb.Controllers
|
||||||
{
|
{
|
||||||
@@ -17,14 +20,20 @@ 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;
|
||||||
|
private readonly IOrderService _orderService;
|
||||||
|
|
||||||
public BasketController(IBasketService basketService,
|
public BasketController(IBasketService basketService,
|
||||||
|
IOrderService orderService,
|
||||||
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;
|
||||||
|
_orderService = orderService;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
@@ -35,6 +44,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,11 +70,15 @@ namespace Microsoft.eShopWeb.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public async Task<IActionResult> Checkout()
|
[Authorize]
|
||||||
|
public async Task<IActionResult> Checkout(Dictionary<string, int> items)
|
||||||
{
|
{
|
||||||
var basket = await GetBasketViewModelAsync();
|
var basketViewModel = await GetBasketViewModelAsync();
|
||||||
|
await _basketService.SetQuantities(basketViewModel.Id, items);
|
||||||
|
|
||||||
await _basketService.Checkout(basket.Id);
|
await _orderService.CreateOrderAsync(basketViewModel.Id, new Address("123 Main St.", "Kent", "OH", "United States", "44240"));
|
||||||
|
|
||||||
|
await _basketService.DeleteBasketAsync(basketViewModel.Id);
|
||||||
|
|
||||||
return View("Checkout");
|
return View("Checkout");
|
||||||
}
|
}
|
||||||
|
|||||||
96
src/Web/Controllers/OrderController.cs
Normal file
96
src/Web/Controllers/OrderController.cs
Normal file
@@ -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,4 +1,5 @@
|
|||||||
using Microsoft.eShopWeb.ViewModels;
|
using Microsoft.eShopWeb.ViewModels;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace ApplicationCore.Interfaces
|
namespace ApplicationCore.Interfaces
|
||||||
@@ -6,8 +7,9 @@ namespace ApplicationCore.Interfaces
|
|||||||
public interface IBasketService
|
public interface IBasketService
|
||||||
{
|
{
|
||||||
Task<BasketViewModel> GetOrCreateBasketForUser(string userName);
|
Task<BasketViewModel> GetOrCreateBasketForUser(string userName);
|
||||||
Task TransferBasket(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 Checkout(int basketId);
|
Task SetQuantities(int basketId, Dictionary<string, int> quantities);
|
||||||
|
Task DeleteBasketAsync(int basketId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,20 @@
|
|||||||
using System.IO;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore;
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
|
|
||||||
namespace Microsoft.eShopWeb
|
namespace Microsoft.eShopWeb
|
||||||
{
|
{
|
||||||
public class Program
|
public class Program
|
||||||
{
|
{
|
||||||
|
|
||||||
public static void Main(string[] args)
|
public static void Main(string[] args)
|
||||||
{
|
{
|
||||||
var host = new WebHostBuilder()
|
BuildWebHost(args).Run();
|
||||||
.UseKestrel()
|
|
||||||
.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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static IWebHost BuildWebHost(string[] args) =>
|
||||||
|
WebHost.CreateDefaultBuilder(args)
|
||||||
|
.UseUrls("http://0.0.0.0:5106")
|
||||||
|
.UseStartup<Startup>()
|
||||||
|
.Build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using ApplicationCore.Interfaces;
|
using ApplicationCore.Interfaces;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.eShopWeb.ApplicationCore.Entities;
|
using Microsoft.eShopWeb.ApplicationCore.Entities;
|
||||||
using System;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.eShopWeb.ViewModels;
|
using Microsoft.eShopWeb.ViewModels;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@@ -11,23 +10,26 @@ namespace Web.Services
|
|||||||
{
|
{
|
||||||
public class BasketService : IBasketService
|
public class BasketService : IBasketService
|
||||||
{
|
{
|
||||||
private readonly IRepository<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(IRepository<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;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<BasketViewModel> GetOrCreateBasketForUser(string userName)
|
public async Task<BasketViewModel> GetOrCreateBasketForUser(string userName)
|
||||||
{
|
{
|
||||||
var basketSpec = new BasketWithItemsSpecification(userName);
|
var basketSpec = new BasketWithItemsSpecification(userName);
|
||||||
var basket = _basketRepository.List(basketSpec).FirstOrDefault();
|
var basket = (await _basketRepository.ListAsync(basketSpec)).FirstOrDefault();
|
||||||
|
|
||||||
if(basket == null)
|
if(basket == null)
|
||||||
{
|
{
|
||||||
@@ -63,7 +65,7 @@ namespace Web.Services
|
|||||||
public async Task<BasketViewModel> CreateBasketForUser(string userId)
|
public async Task<BasketViewModel> CreateBasketForUser(string userId)
|
||||||
{
|
{
|
||||||
var basket = new Basket() { BuyerId = userId };
|
var basket = new Basket() { BuyerId = userId };
|
||||||
_basketRepository.Add(basket);
|
await _basketRepository.AddAsync(basket);
|
||||||
|
|
||||||
return new BasketViewModel()
|
return new BasketViewModel()
|
||||||
{
|
{
|
||||||
@@ -75,30 +77,41 @@ namespace Web.Services
|
|||||||
|
|
||||||
public async Task AddItemToBasket(int basketId, int catalogItemId, decimal price, int quantity)
|
public async Task AddItemToBasket(int basketId, int catalogItemId, decimal price, int quantity)
|
||||||
{
|
{
|
||||||
var basket = _basketRepository.GetById(basketId);
|
var basket = await _basketRepository.GetByIdAsync(basketId);
|
||||||
|
|
||||||
basket.AddItem(catalogItemId, price, quantity);
|
basket.AddItem(catalogItemId, price, quantity);
|
||||||
|
|
||||||
_basketRepository.Update(basket);
|
await _basketRepository.UpdateAsync(basket);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Checkout(int basketId)
|
public async Task SetQuantities(int basketId, Dictionary<string,int> quantities)
|
||||||
{
|
{
|
||||||
var basket = _basketRepository.GetById(basketId);
|
var basket = await _basketRepository.GetByIdAsync(basketId);
|
||||||
|
foreach (var item in basket.Items)
|
||||||
// TODO: Actually Process the order
|
{
|
||||||
|
if (quantities.TryGetValue(item.Id.ToString(), out var quantity))
|
||||||
_basketRepository.Delete(basket);
|
{
|
||||||
|
_logger.LogWarning($"Updating quantity of item ID:{item.Id} to {quantity}.");
|
||||||
|
item.Quantity = quantity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await _basketRepository.UpdateAsync(basket);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task TransferBasket(string anonymousId, string userName)
|
public async Task DeleteBasketAsync(int basketId)
|
||||||
|
{
|
||||||
|
var basket = await _basketRepository.GetByIdAsync(basketId);
|
||||||
|
|
||||||
|
await _basketRepository.DeleteAsync(basket);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task TransferBasketAsync(string anonymousId, string userName)
|
||||||
{
|
{
|
||||||
var basketSpec = new BasketWithItemsSpecification(anonymousId);
|
var basketSpec = new BasketWithItemsSpecification(anonymousId);
|
||||||
var basket = _basketRepository.List(basketSpec).FirstOrDefault();
|
var basket = (await _basketRepository.ListAsync(basketSpec)).FirstOrDefault();
|
||||||
if (basket == null) return Task.CompletedTask;
|
if (basket == null) return;
|
||||||
basket.BuyerId = userName;
|
basket.BuyerId = userName;
|
||||||
_basketRepository.Update(basket);
|
await _basketRepository.UpdateAsync(basket);
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,15 +15,15 @@ namespace Microsoft.eShopWeb.Services
|
|||||||
{
|
{
|
||||||
private readonly ILogger<CatalogService> _logger;
|
private readonly ILogger<CatalogService> _logger;
|
||||||
private readonly IRepository<CatalogItem> _itemRepository;
|
private readonly IRepository<CatalogItem> _itemRepository;
|
||||||
private readonly IRepository<CatalogBrand> _brandRepository;
|
private readonly IAsyncRepository<CatalogBrand> _brandRepository;
|
||||||
private readonly IRepository<CatalogType> _typeRepository;
|
private readonly IAsyncRepository<CatalogType> _typeRepository;
|
||||||
private readonly IUriComposer _uriComposer;
|
private readonly IUriComposer _uriComposer;
|
||||||
|
|
||||||
public CatalogService(
|
public CatalogService(
|
||||||
ILoggerFactory loggerFactory,
|
ILoggerFactory loggerFactory,
|
||||||
IRepository<CatalogItem> itemRepository,
|
IRepository<CatalogItem> itemRepository,
|
||||||
IRepository<CatalogBrand> brandRepository,
|
IAsyncRepository<CatalogBrand> brandRepository,
|
||||||
IRepository<CatalogType> typeRepository,
|
IAsyncRepository<CatalogType> typeRepository,
|
||||||
IUriComposer uriComposer)
|
IUriComposer uriComposer)
|
||||||
{
|
{
|
||||||
_logger = loggerFactory.CreateLogger<CatalogService>();
|
_logger = loggerFactory.CreateLogger<CatalogService>();
|
||||||
@@ -83,7 +83,7 @@ namespace Microsoft.eShopWeb.Services
|
|||||||
public async Task<IEnumerable<SelectListItem>> GetBrands()
|
public async Task<IEnumerable<SelectListItem>> GetBrands()
|
||||||
{
|
{
|
||||||
_logger.LogInformation("GetBrands called.");
|
_logger.LogInformation("GetBrands called.");
|
||||||
var brands = _brandRepository.List();
|
var brands = await _brandRepository.ListAllAsync();
|
||||||
|
|
||||||
var items = new List<SelectListItem>
|
var items = new List<SelectListItem>
|
||||||
{
|
{
|
||||||
@@ -100,7 +100,7 @@ namespace Microsoft.eShopWeb.Services
|
|||||||
public async Task<IEnumerable<SelectListItem>> GetTypes()
|
public async Task<IEnumerable<SelectListItem>> GetTypes()
|
||||||
{
|
{
|
||||||
_logger.LogInformation("GetTypes called.");
|
_logger.LogInformation("GetTypes called.");
|
||||||
var types = _typeRepository.List();
|
var types = await _typeRepository.ListAllAsync();
|
||||||
var items = new List<SelectListItem>
|
var items = new List<SelectListItem>
|
||||||
{
|
{
|
||||||
new SelectListItem() { Value = null, Text = "All", Selected = true }
|
new SelectListItem() { Value = null, Text = "All", Selected = true }
|
||||||
|
|||||||
@@ -1,41 +1,35 @@
|
|||||||
using Microsoft.eShopWeb.Services;
|
using ApplicationCore.Entities.OrderAggregate;
|
||||||
|
using ApplicationCore.Interfaces;
|
||||||
|
using ApplicationCore.Services;
|
||||||
|
using Infrastructure.Data;
|
||||||
|
using Infrastructure.Identity;
|
||||||
|
using Infrastructure.Logging;
|
||||||
|
using Infrastructure.Services;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
using Microsoft.eShopWeb.Services;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Infrastructure.Identity;
|
using System;
|
||||||
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using ApplicationCore.Interfaces;
|
|
||||||
using Infrastructure.FileSystem;
|
|
||||||
using Infrastructure.Logging;
|
|
||||||
using Microsoft.AspNetCore.Identity;
|
|
||||||
using Web.Services;
|
using Web.Services;
|
||||||
using ApplicationCore.Services;
|
|
||||||
using Infrastructure.Data;
|
|
||||||
|
|
||||||
namespace Microsoft.eShopWeb
|
namespace Microsoft.eShopWeb
|
||||||
{
|
{
|
||||||
public class Startup
|
public class Startup
|
||||||
{
|
{
|
||||||
private IServiceCollection _services;
|
private IServiceCollection _services;
|
||||||
public Startup(IHostingEnvironment env)
|
public Startup(IConfiguration configuration)
|
||||||
{
|
{
|
||||||
var builder = new ConfigurationBuilder()
|
Configuration = configuration;
|
||||||
.SetBasePath(env.ContentRootPath)
|
|
||||||
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
|
|
||||||
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
|
|
||||||
.AddEnvironmentVariables();
|
|
||||||
Configuration = builder.Build();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public IConfigurationRoot Configuration { get; }
|
public IConfiguration Configuration { get; }
|
||||||
|
|
||||||
// This method gets called by the runtime. Use this method to add services to the container.
|
|
||||||
public void ConfigureServices(IServiceCollection services)
|
public void ConfigureServices(IServiceCollection services)
|
||||||
{
|
{
|
||||||
// Requires LocalDB which can be installed with SQL Server Express 2016
|
// Requires LocalDB which can be installed with SQL Server Express 2016
|
||||||
@@ -46,11 +40,6 @@ namespace Microsoft.eShopWeb
|
|||||||
{
|
{
|
||||||
c.UseInMemoryDatabase("Catalog");
|
c.UseInMemoryDatabase("Catalog");
|
||||||
//c.UseSqlServer(Configuration.GetConnectionString("CatalogConnection"));
|
//c.UseSqlServer(Configuration.GetConnectionString("CatalogConnection"));
|
||||||
c.ConfigureWarnings(wb =>
|
|
||||||
{
|
|
||||||
//By default, in this application, we don't want to have client evaluations
|
|
||||||
wb.Log(RelationalEventId.QueryClientEvaluationWarning);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
catch (System.Exception ex )
|
catch (System.Exception ex )
|
||||||
{
|
{
|
||||||
@@ -67,21 +56,28 @@ namespace Microsoft.eShopWeb
|
|||||||
.AddEntityFrameworkStores<AppIdentityDbContext>()
|
.AddEntityFrameworkStores<AppIdentityDbContext>()
|
||||||
.AddDefaultTokenProviders();
|
.AddDefaultTokenProviders();
|
||||||
|
|
||||||
|
services.ConfigureApplicationCookie(options =>
|
||||||
|
{
|
||||||
|
options.Cookie.HttpOnly = true;
|
||||||
|
options.ExpireTimeSpan = TimeSpan.FromHours(1);
|
||||||
|
options.LoginPath = "/Account/Signin";
|
||||||
|
options.LogoutPath = "/Account/Signout";
|
||||||
|
});
|
||||||
|
|
||||||
services.AddScoped(typeof(IRepository<>), typeof(EfRepository<>));
|
services.AddScoped(typeof(IRepository<>), typeof(EfRepository<>));
|
||||||
|
services.AddScoped(typeof(IAsyncRepository<>), typeof(EfRepository<>));
|
||||||
|
|
||||||
services.AddMemoryCache();
|
services.AddMemoryCache();
|
||||||
services.AddScoped<ICatalogService, CachedCatalogService>();
|
services.AddScoped<ICatalogService, CachedCatalogService>();
|
||||||
services.AddScoped<IBasketService, BasketService>();
|
services.AddScoped<IBasketService, BasketService>();
|
||||||
|
services.AddScoped<IOrderService, OrderService>();
|
||||||
|
services.AddScoped<IOrderRepository, OrderRepository>();
|
||||||
services.AddScoped<CatalogService>();
|
services.AddScoped<CatalogService>();
|
||||||
services.Configure<CatalogSettings>(Configuration);
|
services.Configure<CatalogSettings>(Configuration);
|
||||||
services.AddSingleton<IUriComposer>(new UriComposer(Configuration.Get<CatalogSettings>()));
|
services.AddSingleton<IUriComposer>(new UriComposer(Configuration.Get<CatalogSettings>()));
|
||||||
|
|
||||||
// TODO: Remove
|
|
||||||
services.AddSingleton<IImageService, LocalFileImageService>();
|
|
||||||
|
|
||||||
services.AddScoped(typeof(IAppLogger<>), typeof(LoggerAdapter<>));
|
services.AddScoped(typeof(IAppLogger<>), typeof(LoggerAdapter<>));
|
||||||
|
|
||||||
|
|
||||||
// Add memory cache services
|
// Add memory cache services
|
||||||
services.AddMemoryCache();
|
services.AddMemoryCache();
|
||||||
|
|
||||||
@@ -98,25 +94,8 @@ namespace Microsoft.eShopWeb
|
|||||||
{
|
{
|
||||||
app.UseDeveloperExceptionPage();
|
app.UseDeveloperExceptionPage();
|
||||||
app.UseBrowserLink();
|
app.UseBrowserLink();
|
||||||
|
ListAllRegisteredServices(app);
|
||||||
app.Map("/allservices", builder => builder.Run(async context =>
|
app.UseDatabaseErrorPage();
|
||||||
{
|
|
||||||
var sb = new StringBuilder();
|
|
||||||
sb.Append("<h1>All Services</h1>");
|
|
||||||
sb.Append("<table><thead>");
|
|
||||||
sb.Append("<tr><th>Type</th><th>Lifetime</th><th>Instance</th></tr>");
|
|
||||||
sb.Append("</thead><tbody>");
|
|
||||||
foreach (var svc in _services)
|
|
||||||
{
|
|
||||||
sb.Append("<tr>");
|
|
||||||
sb.Append($"<td>{svc.ServiceType.FullName}</td>");
|
|
||||||
sb.Append($"<td>{svc.Lifetime}</td>");
|
|
||||||
sb.Append($"<td>{svc.ImplementationType?.FullName}</td>");
|
|
||||||
sb.Append("</tr>");
|
|
||||||
}
|
|
||||||
sb.Append("</tbody></table>");
|
|
||||||
await context.Response.WriteAsync(sb.ToString());
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -124,20 +103,43 @@ namespace Microsoft.eShopWeb
|
|||||||
}
|
}
|
||||||
|
|
||||||
app.UseStaticFiles();
|
app.UseStaticFiles();
|
||||||
app.UseIdentity();
|
app.UseAuthentication();
|
||||||
|
|
||||||
app.UseMvc();
|
app.UseMvc();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ListAllRegisteredServices(IApplicationBuilder app)
|
||||||
|
{
|
||||||
|
app.Map("/allservices", builder => builder.Run(async context =>
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
sb.Append("<h1>All Services</h1>");
|
||||||
|
sb.Append("<table><thead>");
|
||||||
|
sb.Append("<tr><th>Type</th><th>Lifetime</th><th>Instance</th></tr>");
|
||||||
|
sb.Append("</thead><tbody>");
|
||||||
|
foreach (var svc in _services)
|
||||||
|
{
|
||||||
|
sb.Append("<tr>");
|
||||||
|
sb.Append($"<td>{svc.ServiceType.FullName}</td>");
|
||||||
|
sb.Append($"<td>{svc.Lifetime}</td>");
|
||||||
|
sb.Append($"<td>{svc.ImplementationType?.FullName}</td>");
|
||||||
|
sb.Append("</tr>");
|
||||||
|
}
|
||||||
|
sb.Append("</tbody></table>");
|
||||||
|
await context.Response.WriteAsync(sb.ToString());
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
public void ConfigureDevelopment(IApplicationBuilder app,
|
public void ConfigureDevelopment(IApplicationBuilder app,
|
||||||
IHostingEnvironment env,
|
IHostingEnvironment env,
|
||||||
ILoggerFactory loggerFactory,
|
ILoggerFactory loggerFactory,
|
||||||
UserManager<ApplicationUser> userManager)
|
UserManager<ApplicationUser> userManager,
|
||||||
|
CatalogContext catalogContext)
|
||||||
{
|
{
|
||||||
Configure(app, env);
|
Configure(app, env);
|
||||||
|
|
||||||
//Seed Data
|
//Seed Data
|
||||||
CatalogContextSeed.SeedAsync(app, loggerFactory)
|
CatalogContextSeed.SeedAsync(app, catalogContext, loggerFactory)
|
||||||
.Wait();
|
.Wait();
|
||||||
|
|
||||||
var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" };
|
var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" };
|
||||||
@@ -155,17 +157,17 @@ namespace Microsoft.eShopWeb
|
|||||||
public void ConfigureProduction(IApplicationBuilder app,
|
public void ConfigureProduction(IApplicationBuilder app,
|
||||||
IHostingEnvironment env,
|
IHostingEnvironment env,
|
||||||
ILoggerFactory loggerFactory,
|
ILoggerFactory loggerFactory,
|
||||||
UserManager<ApplicationUser> userManager)
|
UserManager<ApplicationUser> userManager,
|
||||||
|
CatalogContext catalogContext)
|
||||||
{
|
{
|
||||||
Configure(app, env);
|
Configure(app, env);
|
||||||
|
|
||||||
//Seed Data
|
//Seed Data
|
||||||
CatalogContextSeed.SeedAsync(app, loggerFactory)
|
CatalogContextSeed.SeedAsync(app, catalogContext, loggerFactory)
|
||||||
.Wait();
|
.Wait();
|
||||||
|
|
||||||
var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" };
|
var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" };
|
||||||
userManager.CreateAsync(defaultUser, "Pass@word1").Wait();
|
userManager.CreateAsync(defaultUser, "Pass@word1").Wait();
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
52
src/Web/ViewComponents/Basket.cs
Normal file
52
src/Web/ViewComponents/Basket.cs
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
src/Web/ViewModels/BasketComponentViewModel.cs
Normal file
7
src/Web/ViewModels/BasketComponentViewModel.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace Microsoft.eShopWeb.ViewModels
|
||||||
|
{
|
||||||
|
public class BasketComponentViewModel
|
||||||
|
{
|
||||||
|
public int ItemsCount { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
23
src/Web/ViewModels/OrderItemViewModel.cs
Normal file
23
src/Web/ViewModels/OrderItemViewModel.cs
Normal file
@@ -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; }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
21
src/Web/ViewModels/OrderViewModel.cs
Normal file
21
src/Web/ViewModels/OrderViewModel.cs
Normal file
@@ -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>();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
namespace Microsoft.eShopWeb.ViewModels
|
namespace Microsoft.eShopWeb.ViewModels
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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" />
|
||||||
|
|||||||
88
src/Web/Views/Order/Detail.cshtml
Normal file
88
src/Web/Views/Order/Detail.cshtml
Normal file
@@ -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>
|
||||||
39
src/Web/Views/Order/Index.cshtml
Normal file
39
src/Web/Views/Order/Index.cshtml
Normal file
@@ -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>
|
||||||
17
src/Web/Views/Shared/Components/Basket/Default.cshtml
Normal file
17
src/Web/Views/Shared/Components/Basket/Default.cshtml
Normal file
@@ -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>
|
||||||
@@ -15,8 +15,12 @@
|
|||||||
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/catalog/pager.css" />
|
<link rel="stylesheet" href="~/css/app.component.css" />
|
||||||
<link rel="stylesheet" href="~/css/catalog/catalog.component.css" />
|
<link rel="stylesheet" href="~/css/basket/basket.component.css" />
|
||||||
|
<link rel="stylesheet" href="~/css/catalog/pager.css" />
|
||||||
|
<link rel="stylesheet" href="~/css/catalog/catalog.component.css" />
|
||||||
|
<link rel="stylesheet" href="~/css/basket/basket-status/basket-status.component.css" />
|
||||||
|
<link rel="stylesheet" href="~/css/orders/orders.component.css" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header class="navbar navbar-light navbar-static-top">
|
<header class="navbar navbar-light navbar-static-top">
|
||||||
@@ -43,7 +47,6 @@
|
|||||||
<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" />
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="col-sm-6">
|
<section class="col-sm-6">
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
@using Microsoft.AspNetCore.Identity
|
@using Microsoft.AspNetCore.Identity
|
||||||
@*@inject IIdentityParser<ApplicationUser> UserManager*@
|
|
||||||
|
|
||||||
@if (Context.User.Identity.IsAuthenticated)
|
@if (Context.User.Identity.IsAuthenticated)
|
||||||
{
|
{
|
||||||
@@ -14,13 +13,13 @@
|
|||||||
|
|
||||||
<section class="esh-identity-drop">
|
<section class="esh-identity-drop">
|
||||||
|
|
||||||
@*<a class="esh-identity-item"
|
<a class="esh-identity-item"
|
||||||
asp-controller="Order"
|
asp-controller="Order"
|
||||||
asp-action="Index">
|
asp-action="Index">
|
||||||
|
|
||||||
<div class="esh-identity-name esh-identity-name--upper">My orders</div>
|
<div class="esh-identity-name esh-identity-name--upper">My orders</div>
|
||||||
<img class="esh-identity-image" src="~/images/my_orders.png">
|
<img class="esh-identity-image" src="~/images/my_orders.png">
|
||||||
</a>*@
|
</a>
|
||||||
|
|
||||||
<a class="esh-identity-item"
|
<a class="esh-identity-item"
|
||||||
href="javascript:document.getElementById('logoutForm').submit()">
|
href="javascript:document.getElementById('logoutForm').submit()">
|
||||||
@@ -33,9 +32,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@*<section class="col-lg-1 col-xs-12">
|
<section class="col-lg-1 col-xs-12">
|
||||||
@await Component.InvokeAsync("Basket", new { user = UserManager.Parse(User) })
|
@await Component.InvokeAsync("Basket", User.Identity.Name)
|
||||||
</section>*@
|
</section>
|
||||||
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -53,5 +52,7 @@ else
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@*<section class="col-lg-1 col-xs-12"></section>*@
|
<section class="col-lg-1 col-xs-12">
|
||||||
|
@await Component.InvokeAsync("Basket")
|
||||||
|
</section>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,41 +1,21 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netcoreapp1.1</TargetFramework>
|
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<PackageTargetFallback>$(PackageTargetFallback);portable-net45+win8+wp8+wpa81;</PackageTargetFallback>
|
<RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
|
||||||
<RuntimeFrameworkVersion>1.1.0</RuntimeFrameworkVersion>
|
|
||||||
<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.AspNetCore.All" Version="2.0.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore" Version="1.1.2" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.0.0" PrivateAssets="All" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.3" />
|
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.0.0" PrivateAssets="All" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Session" Version="1.1.2" />
|
|
||||||
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="1.1.2" />
|
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="1.1.2" />
|
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="1.1.2" />
|
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="1.1.2" />
|
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="1.1.2" />
|
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="1.1.2" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="1.1.2" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="1.1.2" />
|
|
||||||
<PackageReference Include="Microsoft.VisualStudio.Web.BrowserLink" Version="1.1.2" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="Views\Catalog\" />
|
<Folder Include="Views\Catalog\" />
|
||||||
<Folder Include="wwwroot\css\catalog\" />
|
|
||||||
<Folder Include="wwwroot\fonts\" />
|
<Folder Include="wwwroot\fonts\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -43,9 +23,9 @@
|
|||||||
<ProjectReference Include="..\Infrastructure\Infrastructure.csproj" />
|
<ProjectReference Include="..\Infrastructure\Infrastructure.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools" Version="1.1.1" />
|
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.0" />
|
||||||
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.0.1" />
|
<DotNetCliToolReference Include="Microsoft.Extensions.SecretManager.Tools" Version="2.0.0" />
|
||||||
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Design" Version="1.1.2" />
|
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="wwwroot\images\products\1.png" />
|
<None Include="wwwroot\images\products\1.png" />
|
||||||
|
|||||||
42
src/Web/compilerconfig.json
Normal file
42
src/Web/compilerconfig.json
Normal file
@@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
65
src/Web/wwwroot/css/_variables.scss
Normal file
65
src/Web/wwwroot/css/_variables.scss
Normal file
@@ -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';
|
||||||
11
src/Web/wwwroot/css/app.component.css
Normal file
11
src/Web/wwwroot/css/app.component.css
Normal file
@@ -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; }
|
||||||
|
|
||||||
1
src/Web/wwwroot/css/app.component.min.css
vendored
Normal file
1
src/Web/wwwroot/css/app.component.min.css
vendored
Normal file
@@ -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}
|
||||||
23
src/Web/wwwroot/css/app.component.scss
Normal file
23
src/Web/wwwroot/css/app.component.scss
Normal file
@@ -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; }
|
||||||
|
|
||||||
1
src/Web/wwwroot/css/basket/basket-status/basket-status.component.min.css
vendored
Normal file
1
src/Web/wwwroot/css/basket/basket-status/basket-status.component.min.css
vendored
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
49
src/Web/wwwroot/css/basket/basket.component.css
Normal file
49
src/Web/wwwroot/css/basket/basket.component.css
Normal file
@@ -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; }
|
||||||
|
|
||||||
1
src/Web/wwwroot/css/basket/basket.component.min.css
vendored
Normal file
1
src/Web/wwwroot/css/basket/basket.component.min.css
vendored
Normal file
@@ -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}
|
||||||
89
src/Web/wwwroot/css/basket/basket.component.scss
Normal file
89
src/Web/wwwroot/css/basket/basket.component.scss
Normal file
@@ -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-image: url("../../images/main_banner.png");
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
height: 260px;
|
height: 260px;
|
||||||
width: 100%;
|
width: 100%; }
|
||||||
}
|
|
||||||
|
|
||||||
.esh-catalog-title {
|
.esh-catalog-title {
|
||||||
position: relative;
|
position: relative;
|
||||||
top: 74.28571px;
|
top: 74.28571px; }
|
||||||
}
|
|
||||||
|
|
||||||
.esh-catalog-filters {
|
.esh-catalog-filters {
|
||||||
background-color: #00A69C;
|
background-color: #00A69C;
|
||||||
height: 65px;
|
height: 65px; }
|
||||||
}
|
|
||||||
|
|
||||||
.esh-catalog-filter {
|
.esh-catalog-filter {
|
||||||
background-color: transparent;
|
-webkit-appearance: none;
|
||||||
border-color: #00d9cc;
|
background-color: transparent;
|
||||||
color: #FFFFFF;
|
border-color: #00d9cc;
|
||||||
cursor: pointer;
|
color: #FFFFFF;
|
||||||
margin-right: 1rem;
|
cursor: pointer;
|
||||||
margin-top: .5rem;
|
margin-right: 1rem;
|
||||||
outline-color: #83D01B;
|
margin-top: .5rem;
|
||||||
padding-bottom: 0;
|
min-width: 140px;
|
||||||
padding-left: 0.5rem;
|
outline-color: #83D01B;
|
||||||
padding-right: 0.5rem;
|
padding-bottom: 0;
|
||||||
padding-top: 1.5rem;
|
padding-left: 0.5rem;
|
||||||
min-width: 140px;
|
padding-right: 0.5rem;
|
||||||
-webkit-appearance: none;
|
padding-top: 1.5rem; }
|
||||||
}
|
.esh-catalog-filter option {
|
||||||
|
background-color: #00A69C; }
|
||||||
.esh-catalog-filter option {
|
|
||||||
background-color: #00A69C;
|
|
||||||
}
|
|
||||||
|
|
||||||
.esh-catalog-label {
|
.esh-catalog-label {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 0;
|
z-index: 0; }
|
||||||
}
|
.esh-catalog-label::before {
|
||||||
|
color: rgba(255, 255, 255, 0.5);
|
||||||
.esh-catalog-label::before {
|
content: attr(data-title);
|
||||||
color: rgba(255, 255, 255, 0.5);
|
font-size: 0.65rem;
|
||||||
content: attr(data-title);
|
margin-left: 0.5rem;
|
||||||
font-size: 0.65rem;
|
margin-top: 0.65rem;
|
||||||
margin-top: 0.65rem;
|
position: absolute;
|
||||||
margin-left: 0.5rem;
|
text-transform: uppercase;
|
||||||
position: absolute;
|
z-index: 1; }
|
||||||
text-transform: uppercase;
|
.esh-catalog-label::after {
|
||||||
z-index: 1;
|
background-image: url("../../images/arrow-down.png");
|
||||||
}
|
content: '';
|
||||||
|
height: 7px;
|
||||||
.esh-catalog-label::after {
|
position: absolute;
|
||||||
background-image: url("../../images/arrow-down.png");
|
right: 1.5rem;
|
||||||
height: 7px;
|
top: 2.5rem;
|
||||||
content: '';
|
width: 10px;
|
||||||
position: absolute;
|
z-index: 1; }
|
||||||
right: 1.5rem;
|
|
||||||
top: 2.5rem;
|
|
||||||
width: 10px;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.esh-catalog-send {
|
.esh-catalog-send {
|
||||||
background-color: #83D01B;
|
background-color: #83D01B;
|
||||||
color: #FFFFFF;
|
color: #FFFFFF;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
transform: translateY(.5rem);
|
margin-top: -1.5rem;
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
transition: all 0.35s;
|
transition: all 0.35s; }
|
||||||
}
|
.esh-catalog-send:hover {
|
||||||
|
background-color: #4a760f;
|
||||||
.esh-catalog-send:hover {
|
transition: all 0.35s; }
|
||||||
background-color: #4a760f;
|
|
||||||
transition: all 0.35s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.esh-catalog-items {
|
.esh-catalog-items {
|
||||||
margin-top: 1rem;
|
margin-top: 1rem; }
|
||||||
}
|
|
||||||
|
|
||||||
.esh-catalog-item {
|
.esh-catalog-item {
|
||||||
text-align: center;
|
margin-bottom: 1.5rem;
|
||||||
margin-bottom: 1.5rem;
|
text-align: center;
|
||||||
width: 33%;
|
width: 33%;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
float: none !important;
|
float: none !important; }
|
||||||
}
|
@media screen and (max-width: 1024px) {
|
||||||
|
|
||||||
@media screen and (max-width: 1024px) {
|
|
||||||
.esh-catalog-item {
|
.esh-catalog-item {
|
||||||
width: 50%;
|
width: 50%; } }
|
||||||
}
|
@media screen and (max-width: 768px) {
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 768px) {
|
|
||||||
.esh-catalog-item {
|
.esh-catalog-item {
|
||||||
width: 100%;
|
width: 100%; } }
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.esh-catalog-thumbnail {
|
.esh-catalog-thumbnail {
|
||||||
max-width: 370px;
|
max-width: 370px;
|
||||||
width: 100%;
|
width: 100%; }
|
||||||
}
|
|
||||||
|
|
||||||
.esh-catalog-button {
|
.esh-catalog-button {
|
||||||
background-color: #83D01B;
|
background-color: #83D01B;
|
||||||
border: none;
|
border: 0;
|
||||||
color: #FFFFFF;
|
color: #FFFFFF;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
height: 3rem;
|
height: 3rem;
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
transition: all 0.35s;
|
transition: all 0.35s;
|
||||||
width: 80%;
|
width: 80%; }
|
||||||
}
|
.esh-catalog-button.is-disabled {
|
||||||
.esh-catalog-button.is-disabled {
|
opacity: .5;
|
||||||
opacity: .5;
|
pointer-events: none; }
|
||||||
pointer-events: none;
|
.esh-catalog-button:hover {
|
||||||
}
|
background-color: #4a760f;
|
||||||
|
transition: all 0.35s; }
|
||||||
.esh-catalog-button:hover {
|
|
||||||
background-color: #4a760f;
|
|
||||||
transition: all 0.35s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.esh-catalog-name {
|
.esh-catalog-name {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
margin-top: .5rem;
|
margin-top: .5rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase; }
|
||||||
}
|
|
||||||
|
|
||||||
.esh-catalog-price {
|
.esh-catalog-price {
|
||||||
text-align: center;
|
font-size: 28px;
|
||||||
font-weight: 900;
|
font-weight: 900;
|
||||||
font-size: 28px;
|
text-align: center; }
|
||||||
}
|
.esh-catalog-price::before {
|
||||||
|
content: '$'; }
|
||||||
|
|
||||||
.esh-catalog-price::before {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
1
src/Web/wwwroot/css/catalog/catalog.component.min.css
vendored
Normal file
1
src/Web/wwwroot/css/catalog/catalog.component.min.css
vendored
Normal file
@@ -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:'$'}
|
||||||
154
src/Web/wwwroot/css/catalog/catalog.component.scss
Normal file
154
src/Web/wwwroot/css/catalog/catalog.component.scss
Normal file
@@ -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: '$';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
50
src/Web/wwwroot/css/orders/orders.component.css
Normal file
50
src/Web/wwwroot/css/orders/orders.component.css
Normal file
@@ -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; }
|
||||||
|
|
||||||
1
src/Web/wwwroot/css/orders/orders.component.min.css
vendored
Normal file
1
src/Web/wwwroot/css/orders/orders.component.min.css
vendored
Normal file
@@ -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}
|
||||||
91
src/Web/wwwroot/css/orders/orders.component.scss
Normal file
91
src/Web/wwwroot/css/orders/orders.component.scss
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 5.1 KiB |
@@ -1,14 +1,19 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netcoreapp1.1</TargetFramework>
|
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.3.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.3.0" />
|
||||||
<PackageReference Include="xunit" Version="2.2.0" />
|
<PackageReference Include="xunit" Version="2.2.0" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" />
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="1.1.2" />
|
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="2.0.0" />
|
||||||
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +1,19 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netcoreapp1.1</TargetFramework>
|
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.3.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.3.0" />
|
||||||
<PackageReference Include="xunit" Version="2.2.0" />
|
<PackageReference Include="xunit" Version="2.2.0" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" />
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" />
|
||||||
<PackageReference Include="Moq" Version="4.7.49" />
|
<PackageReference Include="Moq" Version="4.7.99" />
|
||||||
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netcoreapp1.1</TargetFramework>
|
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore" Version="1.1.2" />
|
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.0.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.3" />
|
|
||||||
|
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.3.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.3.0" />
|
||||||
<PackageReference Include="Moq" Version="4.7.49" />
|
<PackageReference Include="Moq" Version="4.7.99" />
|
||||||
<PackageReference Include="xunit" Version="2.2.0" />
|
<PackageReference Include="xunit" Version="2.2.0" />
|
||||||
<PackageReference Include="xunit.runner.console" Version="2.2.0" />
|
<PackageReference Include="xunit.runner.console" Version="2.2.0" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" />
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" />
|
||||||
|
|||||||
Reference in New Issue
Block a user